From 92d342a645d0d0df9605afa761739e46c00a5261 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Tue, 9 Jun 2026 10:38:27 -0500 Subject: [PATCH 1/2] Fix ui flashes from on prop changes --- .../_components/animated-provider-steps.tsx | 19 +++--------- .../_components/onboarding-step.tsx | 26 ++++++++++------ .../cloud-picker/unified-cloud-picker.tsx | 31 ++----------------- frontend/components/task-dialog/file-list.tsx | 10 +++--- .../components/task-dialog/use-task-dialog.ts | 23 +++++++------- 5 files changed, 39 insertions(+), 70 deletions(-) diff --git a/frontend/app/onboarding/_components/animated-provider-steps.tsx b/frontend/app/onboarding/_components/animated-provider-steps.tsx index 27ddde543..b133ada76 100644 --- a/frontend/app/onboarding/_components/animated-provider-steps.tsx +++ b/frontend/app/onboarding/_components/animated-provider-steps.tsx @@ -27,16 +27,15 @@ export function AnimatedProviderSteps({ processingStartTime?: number | null; hasError?: boolean; }) { - const [startTime, setStartTime] = useState(null); + const [prevIsCompleted, setPrevIsCompleted] = useState(isCompleted); const [elapsedTime, setElapsedTime] = useState(0); - // Initialize start time from prop - useEffect(() => { + if (isCompleted && !prevIsCompleted) { + setPrevIsCompleted(true); if (processingStartTime) { - // Use the start time passed from parent (when user clicked Complete) - setStartTime(processingStartTime); + setElapsedTime(Date.now() - processingStartTime); } - }, [processingStartTime]); + } // Progress through steps useEffect(() => { @@ -48,14 +47,6 @@ export function AnimatedProviderSteps({ } }, [currentStep, setCurrentStep, steps, isCompleted]); - // Calculate elapsed time when completed - useEffect(() => { - if (isCompleted && startTime) { - const elapsed = Date.now() - startTime; - setElapsedTime(elapsed); - } - }, [isCompleted, startTime]); - const isDone = currentStep >= steps.length && !isCompleted && !hasError; return ( diff --git a/frontend/app/onboarding/_components/onboarding-step.tsx b/frontend/app/onboarding/_components/onboarding-step.tsx index 36bf9900a..4d52f9914 100644 --- a/frontend/app/onboarding/_components/onboarding-step.tsx +++ b/frontend/app/onboarding/_components/onboarding-step.tsx @@ -37,24 +37,30 @@ export function OnboardingStep({ }: OnboardingStepProps) { const [displayedText, setDisplayedText] = useState(""); const [showChildren, setShowChildren] = useState(false); + const [prev, setPrev] = useState({ text, isVisible, isCompleted }); - useEffect(() => { + if ( + text !== prev.text || + isVisible !== prev.isVisible || + isCompleted !== prev.isCompleted + ) { + setPrev({ text, isVisible, isCompleted }); if (!isVisible) { setDisplayedText(""); setShowChildren(false); - return; - } - - if (isCompleted) { + } else if (isCompleted) { setDisplayedText(text); setShowChildren(true); - return; + } else { + setDisplayedText(""); + setShowChildren(false); } + } - let currentIndex = 0; - setDisplayedText(""); - setShowChildren(false); + useEffect(() => { + if (!isVisible || isCompleted) return; + let currentIndex = 0; const interval = setInterval(() => { if (currentIndex < text.length) { setDisplayedText(text.slice(0, currentIndex + 1)); @@ -63,7 +69,7 @@ export function OnboardingStep({ clearInterval(interval); setShowChildren(true); } - }, 20); // 20ms per character + }, 20); return () => clearInterval(interval); }, [text, isVisible, isCompleted]); diff --git a/frontend/components/cloud-picker/unified-cloud-picker.tsx b/frontend/components/cloud-picker/unified-cloud-picker.tsx index c87d64a72..27a3faafd 100644 --- a/frontend/components/cloud-picker/unified-cloud-picker.tsx +++ b/frontend/components/cloud-picker/unified-cloud-picker.tsx @@ -28,7 +28,6 @@ export const UnifiedCloudPicker = ({ const [isPickerLoaded, setIsPickerLoaded] = useState(false); const [isPickerOpen, setIsPickerOpen] = useState(false); const [isIngestSettingsOpen, setIsIngestSettingsOpen] = useState(false); - const [isLoadingBaseUrl, setIsLoadingBaseUrl] = useState(false); const [autoBaseUrl, setAutoBaseUrl] = useState(undefined); const isControlled = @@ -58,25 +57,9 @@ export const UnifiedCloudPicker = ({ const effectiveBaseUrl = baseUrl || autoBaseUrl; - // Auto-detect base URL for OneDrive personal accounts - // For SharePoint, we require the baseUrl to be provided from the connector config - useEffect(() => { - if (provider === "onedrive" && !baseUrl && accessToken && !autoBaseUrl) { - // Only auto-set for OneDrive, not SharePoint - const getBaseUrl = async () => { - setIsLoadingBaseUrl(true); - try { - setAutoBaseUrl("https://onedrive.live.com/picker"); - } catch (error) { - console.error("Auto-detect baseUrl failed:", error); - } finally { - setIsLoadingBaseUrl(false); - } - }; - - getBaseUrl(); - } - }, [accessToken, baseUrl, autoBaseUrl, provider]); + if (provider === "onedrive" && !baseUrl && accessToken && !autoBaseUrl) { + setAutoBaseUrl("https://onedrive.live.com/picker"); + } // Load picker API useEffect(() => { @@ -155,14 +138,6 @@ export const UnifiedCloudPicker = ({ onFileSelected([]); }; - if (isLoadingBaseUrl) { - return ( -
- Loading... -
- ); - } - if ( (provider === "onedrive" || provider === "sharepoint") && !clientId && diff --git a/frontend/components/task-dialog/file-list.tsx b/frontend/components/task-dialog/file-list.tsx index dc818a7d6..0aacfbd8a 100644 --- a/frontend/components/task-dialog/file-list.tsx +++ b/frontend/components/task-dialog/file-list.tsx @@ -1,7 +1,7 @@ "use client"; import { ArrowUpAZ, ChevronDown, FileText } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import type { TaskFileEntry } from "@/app/api/queries/useGetTasksQuery"; import { useIsCloudBrand } from "@/contexts/brand-context"; import type { Task } from "@/contexts/task-context"; @@ -69,11 +69,9 @@ export function TaskDialogFileList({ const showRetryIngestionsTab = retryIngestionCount > 0; - useEffect(() => { - if (!showRetryIngestionsTab && activeTab === "retry-ingestions") { - setActiveTab("task-ingestions"); - } - }, [showRetryIngestionsTab, activeTab]); + if (!showRetryIngestionsTab && activeTab === "retry-ingestions") { + setActiveTab("task-ingestions"); + } const analysisByPath = useMemo(() => { const map = new Map< diff --git a/frontend/components/task-dialog/use-task-dialog.ts b/frontend/components/task-dialog/use-task-dialog.ts index 0d87275de..70f942cf2 100644 --- a/frontend/components/task-dialog/use-task-dialog.ts +++ b/frontend/components/task-dialog/use-task-dialog.ts @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; import { type RetryTaskResponse, @@ -110,11 +110,13 @@ export function useTaskDialog(open: boolean, taskId: string) { const [expandedPath, setExpandedPath] = useState(null); const [nameSort, setNameSort] = useState("asc"); - useEffect(() => { + const [prevOpen, setPrevOpen] = useState(open); + if (open !== prevOpen) { + setPrevOpen(open); if (!open) { setSelectedPaths(new Set()); } - }, [open]); + } const fileEntries = useMemo( () => (task ? getTaskFileEntries(task) : []), @@ -140,16 +142,13 @@ export function useTaskDialog(open: boolean, taskId: string) { return countTaskFileEntriesByCategory(scopedEntries); }, [task, fileEntries, search, activeFileType]); - useEffect(() => { - if ( - !categoryCounts || - statusCategory === ALL_TASK_STATUS_CATEGORIES || - (categoryCounts[statusCategory as TaskFileStatusCategory] ?? 0) > 0 - ) { - return; - } + if ( + categoryCounts && + statusCategory !== ALL_TASK_STATUS_CATEGORIES && + (categoryCounts[statusCategory as TaskFileStatusCategory] ?? 0) === 0 + ) { setStatusCategory(ALL_TASK_STATUS_CATEGORIES); - }, [categoryCounts, statusCategory]); + } const filteredEntries = useMemo( () => From f32e84b661f3fe3f3a24ad94189dc99df68d8725 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Tue, 9 Jun 2026 16:46:11 -0500 Subject: [PATCH 2/2] coderabbit fixes --- .../app/onboarding/_components/animated-provider-steps.tsx | 4 +++- frontend/app/onboarding/_components/onboarding-step.tsx | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/app/onboarding/_components/animated-provider-steps.tsx b/frontend/app/onboarding/_components/animated-provider-steps.tsx index b133ada76..da9bc5e20 100644 --- a/frontend/app/onboarding/_components/animated-provider-steps.tsx +++ b/frontend/app/onboarding/_components/animated-provider-steps.tsx @@ -27,7 +27,7 @@ export function AnimatedProviderSteps({ processingStartTime?: number | null; hasError?: boolean; }) { - const [prevIsCompleted, setPrevIsCompleted] = useState(isCompleted); + const [prevIsCompleted, setPrevIsCompleted] = useState(false); const [elapsedTime, setElapsedTime] = useState(0); if (isCompleted && !prevIsCompleted) { @@ -35,6 +35,8 @@ export function AnimatedProviderSteps({ if (processingStartTime) { setElapsedTime(Date.now() - processingStartTime); } + } else if (!isCompleted && prevIsCompleted) { + setPrevIsCompleted(false); } // Progress through steps diff --git a/frontend/app/onboarding/_components/onboarding-step.tsx b/frontend/app/onboarding/_components/onboarding-step.tsx index 4d52f9914..b2941f6b4 100644 --- a/frontend/app/onboarding/_components/onboarding-step.tsx +++ b/frontend/app/onboarding/_components/onboarding-step.tsx @@ -35,8 +35,11 @@ export function OnboardingStep({ isMarkdown = false, hideIcon = false, }: OnboardingStepProps) { - const [displayedText, setDisplayedText] = useState(""); - const [showChildren, setShowChildren] = useState(false); + const completedOnMount = isVisible && isCompleted && !isMarkdown; + const [displayedText, setDisplayedText] = useState( + completedOnMount ? text : "", + ); + const [showChildren, setShowChildren] = useState(completedOnMount); const [prev, setPrev] = useState({ text, isVisible, isCompleted }); if (