From 0b77eec1ca45827d7efbde7cb9444d21eede5ec8 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Wed, 1 Apr 2026 06:36:20 +0000 Subject: [PATCH] refactor: collapse Flows nav, remove session recovery from library, fix race conditions - Collapse "Guided Flows" + "Troubleshooting" sidebar items into single "Flow Library" entry - Remove incomplete session recovery and "Repeat Last" sections from TreeLibraryPage - Fix handleSearch race: now participates in shared loadTreesRequestId guard so stale search results can't overwrite newer filter results - Fix Sidebar refreshStats race: add statsRequestId ref to discard stale badge count responses Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/layout/Sidebar.tsx | 14 +-- frontend/src/pages/TreeLibraryPage.tsx | 109 ++------------------- 2 files changed, 16 insertions(+), 107 deletions(-) diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index c1199dff..506438a5 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -47,11 +47,15 @@ export function Sidebar() { const [flyoutIndex, setFlyoutIndex] = useState(null) const flyoutTimeout = useRef | null>(null) const sidebarRef = useRef(null) + const statsRequestId = useRef(0) /* ── Stats fetching ───────────────────────────────── */ const refreshStats = useCallback(() => { - sidebarApi.getStats().then(setStats).catch(() => {}) + const requestId = ++statsRequestId.current + sidebarApi.getStats() + .then(data => { if (requestId === statsRequestId.current) setStats(data) }) + .catch(() => {}) }, []) useEffect(() => { refreshStats() }, [location.pathname, refreshStats]) @@ -84,8 +88,7 @@ export function Sidebar() { badge: stats?.tree_counts.total || undefined, matchPaths: ['/trees', '/flows', '/my-trees', '/step-library', '/review-queue'], children: [ - { href: '/trees', label: 'Guided Flows', count: stats?.tree_counts.total || undefined }, - { href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined }, + { href: '/trees', label: 'Flow Library', count: stats?.tree_counts.total || undefined }, { href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined }, { href: '/step-library', label: 'Solutions Library' }, { href: '/review-queue', label: 'Review Queue' }, @@ -123,12 +126,11 @@ export function Sidebar() { title: 'KNOWLEDGE', items: [ { - href: '/trees', icon: GitBranch, label: 'Guided Flows', shortLabel: 'Flows', + href: '/trees', icon: GitBranch, label: 'Flow Library', shortLabel: 'Flows', badge: stats?.tree_counts.total || undefined, matchPaths: ['/trees', '/flows', '/my-trees'], children: [ - { href: '/trees', label: 'All Flows' }, - { href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined }, + { href: '/trees', label: 'Flow Library' }, { href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined }, ], }, diff --git a/frontend/src/pages/TreeLibraryPage.tsx b/frontend/src/pages/TreeLibraryPage.tsx index 594893d4..1c5ab94e 100644 --- a/frontend/src/pages/TreeLibraryPage.tsx +++ b/frontend/src/pages/TreeLibraryPage.tsx @@ -1,14 +1,13 @@ import { useEffect, useState, useCallback, useRef } from 'react' import { useNavigate, useSearchParams } from 'react-router-dom' -import { X, RotateCcw, Play, FileUp } from 'lucide-react' +import { X, FileUp } from 'lucide-react' import { PageMeta } from '@/components/common/PageMeta' import { Button } from '@/components/ui/Button' import { FlowIllustration } from '@/components/common/EmptyStateIllustrations' import { treesApi } from '@/api/trees' import { categoriesApi } from '@/api/categories' import { foldersApi } from '@/api/folders' -import { sessionsApi } from '@/api/sessions' -import type { TreeListItem, CategoryListItem, FolderListItem, Session, IntakeFormField } from '@/types' +import type { TreeListItem, CategoryListItem, FolderListItem, IntakeFormField } from '@/types' import { FolderEditModal } from '@/components/library/FolderEditModal' import { ForkModal } from '@/components/library/ForkModal' import { ExportFlowModal } from '@/components/library/ExportFlowModal' @@ -21,8 +20,8 @@ import { TreeListView } from '@/components/library/TreeListView' import { TreeTableView } from '@/components/library/TreeTableView' import { ViewToggle } from '@/components/library/ViewToggle' import { SortDropdown } from '@/components/library/SortDropdown' -import { cn, safeGetItem } from '@/lib/utils' -import { getSessionResumePath, getTreeNavigatePath } from '@/lib/routing' +import { cn } from '@/lib/utils' +import { getTreeNavigatePath } from '@/lib/routing' import { usePermissions } from '@/hooks/usePermissions' import { useUserPreferencesStore } from '@/store/userPreferencesStore' import { CreateFlowDropdown } from '@/components/common/CreateFlowDropdown' @@ -93,24 +92,6 @@ export function TreeLibraryPage() { // AI builder state - - // Repeat Last Session - const lastSessionData = (() => { - const raw = safeGetItem('last-session') - if (!raw) return null - try { return JSON.parse(raw) as { tree_id: string; tree_name: string; client_name: string; ticket_number: string; tree_type?: string } } - catch { return null } - })() - - // Incomplete sessions for auto-recovery - const [incompleteSessions, setIncompleteSessions] = useState([]) - const [dismissedSessionIds, setDismissedSessionIds] = useState>(() => { - try { - const raw = sessionStorage.getItem('dismissed-sessions') - return raw ? new Set(JSON.parse(raw) as string[]) : new Set() - } catch { return new Set() } - }) - const loadFolders = useCallback(async () => { try { const foldersData = await foldersApi.list() @@ -120,30 +101,6 @@ export function TreeLibraryPage() { } }, []) - // Load incomplete sessions on mount - useEffect(() => { - sessionsApi.list({ completed: false, size: 5 }) - .then(setIncompleteSessions) - .catch((err) => console.error('Failed to load incomplete sessions:', err)) - }, []) - - const dismissSession = (sessionId: string) => { - const next = new Set(dismissedSessionIds) - next.add(sessionId) - setDismissedSessionIds(next) - try { sessionStorage.setItem('dismissed-sessions', JSON.stringify([...next])) } catch { /* */ } - } - - const visibleIncompleteSessions = incompleteSessions.filter(s => !dismissedSessionIds.has(s.id)) - - const formatTimeAgo = (dateString: string) => { - const diff = Math.floor((Date.now() - new Date(dateString).getTime()) / 1000) - if (diff < 60) return 'just now' - if (diff < 3600) return `${Math.floor(diff / 60)} min ago` - if (diff < 86400) return `${Math.floor(diff / 3600)} hr ago` - return `${Math.floor(diff / 86400)} days ago` - } - // Load categories once on mount (they rarely change) useEffect(() => { categoriesApi.list() @@ -194,15 +151,18 @@ export function TreeLibraryPage() { loadTrees() return } + const requestId = ++loadTreesRequestId.current setIsLoading(true) try { const results = await treesApi.search(searchQuery) + if (requestId !== loadTreesRequestId.current) return setTrees(results) } catch (err) { + if (requestId !== loadTreesRequestId.current) return toast.error('Failed to search flows') console.error(err) } finally { - setIsLoading(false) + if (requestId === loadTreesRequestId.current) setIsLoading(false) } } @@ -430,59 +390,6 @@ export function TreeLibraryPage() { )} - {/* Incomplete Session Recovery */} - {visibleIncompleteSessions.length > 0 && ( -
- {visibleIncompleteSessions.map(s => ( -
-
-

- {s.tree_snapshot?.name || 'Unknown tree'} -

-

- {s.client_name && `${s.client_name} · `} - {s.started_at ? `Started ${formatTimeAgo(s.started_at)}` : 'Not started'} -

-
-
- - -
-
- ))} -
- )} - - {/* Repeat Last Session */} - {lastSessionData && ( -
- -
- )} - {/* Loading State */} {isLoading ? (