From c43c05c29bb6d0df93f0f0b2f96bffebeb85cb62 Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Sat, 18 Jul 2026 12:10:06 +0700 Subject: [PATCH] fix(flows): don't bounce off a working flow on a partial load error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flow detail query runs with errorPolicy:'all', so a failed sibling field (the flow's log lists are separate, nullable root query fields — terminalLogs, messageLogs, etc.) surfaces an Apollo error while `flow` itself resolves fine. Both the redirect effect and the provider's load-error toast keyed off "any error present", so one flaky log resolver would kick the user back to /flows and toast "Failed to load flow" over a flow that had actually loaded. Gate both on the flow genuinely being absent (`!flowData?.flow`) instead of on the presence of an error. A missing/invalid flow still redirects and toasts (its non-null `flow` field propagates to a null result); a partial failure now renders the flow and lets the affected panel show its own empty state. Reachability confirmed from the schema (sibling log fields are `[X!]`, nullable; `flow` is `Flow!`, non-null) — end-to-end confirmation folded into the P2-S5 live run. 996 tests pass. Co-Authored-By: Claude Opus 4.8 --- frontend/src/pages/flows/flow.tsx | 9 ++++++--- frontend/src/providers/flow-provider.tsx | 11 +++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/src/pages/flows/flow.tsx b/frontend/src/pages/flows/flow.tsx index e6fac13a..a43fb718 100644 --- a/frontend/src/pages/flows/flow.tsx +++ b/frontend/src/pages/flows/flow.tsx @@ -79,7 +79,7 @@ function Flow() { const { isDesktop, isMobile } = useBreakpoint(); const navigate = useNavigate(); - const { flowData, flowError, flowId, isLoading: isFlowLoading } = useFlow(); + const { flowData, flowId, isLoading: isFlowLoading } = useFlow(); const { deleteFlow, finishFlow } = useFlows(); const { isFavoriteFlow, toggleFavoriteFlow } = useFavorites(); @@ -108,10 +108,13 @@ function Flow() { const [renameFlowMutation, { loading: isRenameLoading }] = useMutation(RenameFlowDocument); useEffect(() => { - if (flowError || (!isFlowLoading && !flowData?.flow)) { + // errorPolicy:'all' surfaces a partial error while the flow itself + // loaded; leave only when the flow is genuinely absent, not on any error + // — else a failed sibling log query bounces the user off a working flow. + if (!isFlowLoading && !flowData?.flow) { navigate(routes.flows, { replace: true }); } - }, [flowError, flowData, isFlowLoading, navigate]); + }, [flowData, isFlowLoading, navigate]); const handleFlowRenameSave = useCallback(async () => { const newTitle = editingInputRef.current?.value.trim(); diff --git a/frontend/src/providers/flow-provider.tsx b/frontend/src/providers/flow-provider.tsx index c2b27327..00a1a4b5 100644 --- a/frontend/src/providers/flow-provider.tsx +++ b/frontend/src/providers/flow-provider.tsx @@ -170,12 +170,11 @@ export function FlowProvider({ children }: FlowProviderProps) { const flowStatus = useMemo(() => flowData?.flow?.status, [flowData?.flow?.status]); - // A single Postgres "no rows in result set" surfaces here every time a sibling - // query/subscription retries against an invalid flow id; without a stable - // toast id Sonner would stack 8 copies of the same message before the page - // redirects. Surface a friendly message and drop the raw SQL detail entirely. + // errorPolicy:'all' surfaces a partial error while the flow loaded, so gate + // on `!flow` or a partial failure toasts over a flow that rendered fine. The + // stable id keeps the invalid-id "no rows" retries from stacking. useEffect(() => { - if (flowError) { + if (flowError && !flowData?.flow) { const raw = flowError.message ?? ''; const isNotFound = /no rows in result set|not found/i.test(raw); toast.error(isNotFound ? 'Flow not found' : 'Failed to load flow', { @@ -184,7 +183,7 @@ export function FlowProvider({ children }: FlowProviderProps) { }); Log.error('Error loading flow:', flowError); } - }, [flowError]); + }, [flowError, flowData]); const submitAutomationMessage = useCallback( async (values: FlowFormValues) => {