feat(FN-4677): add sustained-absence threshold to project validation

Fusion-Task-Id: FN-4677
Fusion-Task-Lineage: b3b9eb56-a76b-469c-9f7b-79232da29807
This commit is contained in:
Fusion (runfusion.ai)
2026-05-15 15:28:10 -07:00
committed by gsxdsm
parent 569eb2eb79
commit f10eac2a18

View File

@@ -4,6 +4,7 @@ import { fetchGlobalSettings, updateGlobalSettings } from "../api";
// Legacy localStorage key for migration - no longer used as primary storage // Legacy localStorage key for migration - no longer used as primary storage
const LEGACY_STORAGE_KEY = "kb-dashboard-current-project"; const LEGACY_STORAGE_KEY = "kb-dashboard-current-project";
export const CONSECUTIVE_ABSENCE_THRESHOLD = 3;
/** /**
* Get the node key used in dashboardCurrentProjectIdByNode. * Get the node key used in dashboardCurrentProjectIdByNode.
@@ -48,6 +49,8 @@ export function useCurrentProject(
const explicitlyClearedRef = useRef(false); const explicitlyClearedRef = useRef(false);
// Cache of current settings to avoid repeated fetches // Cache of current settings to avoid repeated fetches
const settingsCacheRef = useRef<Record<string, string> | null>(null); const settingsCacheRef = useRef<Record<string, string> | null>(null);
// Consecutive poll cycles where current project is missing from availableProjects
const absentCountRef = useRef(0);
const nodeKey = getNodeKey(nodeId); const nodeKey = getNodeKey(nodeId);
@@ -119,6 +122,11 @@ export function useCurrentProject(
}; };
}, [nodeKey, availableProjects]); }, [nodeKey, availableProjects]);
// Reset absence tracking when selection changes
useEffect(() => {
absentCountRef.current = 0;
}, [currentProject?.id]);
// Validate project still exists and persist to global settings // Validate project still exists and persist to global settings
useEffect(() => { useEffect(() => {
if (loading) return; if (loading) return;
@@ -126,10 +134,19 @@ export function useCurrentProject(
if (currentProject) { if (currentProject) {
// Validate project still exists in available projects // Validate project still exists in available projects
const stillExists = availableProjects.some((p) => p.id === currentProject.id); const stillExists = availableProjects.some((p) => p.id === currentProject.id);
if (!stillExists && availableProjects.length > 0) { if (stillExists) {
// Project was unregistered - clear selection and default to first active absentCountRef.current = 0;
const firstActive = availableProjects.find((p) => p.status === "active"); } else if (availableProjects.length === 0) {
setCurrentProjectState(firstActive || availableProjects[0] || null); // Likely a transient total poll failure; keep current selection
absentCountRef.current = 0;
} else {
absentCountRef.current += 1;
if (absentCountRef.current >= CONSECUTIVE_ABSENCE_THRESHOLD) {
// Project was sustainably absent - clear selection and default to first active
absentCountRef.current = 0;
const firstActive = availableProjects.find((p) => p.status === "active");
setCurrentProjectState(firstActive || availableProjects[0] || null);
}
return; return;
} }
@@ -139,12 +156,15 @@ export function useCurrentProject(
updateGlobalSettings({ dashboardCurrentProjectIdByNode: newCache }).catch(() => { updateGlobalSettings({ dashboardCurrentProjectIdByNode: newCache }).catch(() => {
// Non-critical - persistence failed // Non-critical - persistence failed
}); });
} else if (availableProjects.length > 0 && !explicitlyClearedRef.current) { } else {
// No selection but projects available - default to first active absentCountRef.current = 0;
// Skip if user explicitly cleared (navigated to overview) if (availableProjects.length > 0 && !explicitlyClearedRef.current) {
const firstActive = availableProjects.find((p) => p.status === "active"); // No selection but projects available - default to first active
if (firstActive) { // Skip if user explicitly cleared (navigated to overview)
setCurrentProjectState(firstActive); const firstActive = availableProjects.find((p) => p.status === "active");
if (firstActive) {
setCurrentProjectState(firstActive);
}
} }
} }
}, [currentProject, availableProjects, loading, nodeKey]); }, [currentProject, availableProjects, loading, nodeKey]);
@@ -152,6 +172,7 @@ export function useCurrentProject(
const setCurrentProject = useCallback( const setCurrentProject = useCallback(
(project: ProjectInfo | null) => { (project: ProjectInfo | null) => {
explicitlyClearedRef.current = false; explicitlyClearedRef.current = false;
absentCountRef.current = 0;
setCurrentProjectState(project); setCurrentProjectState(project);
if (project) { if (project) {
@@ -167,6 +188,7 @@ export function useCurrentProject(
const clearCurrentProject = useCallback(() => { const clearCurrentProject = useCallback(() => {
explicitlyClearedRef.current = true; explicitlyClearedRef.current = true;
absentCountRef.current = 0;
setCurrentProjectState(null); setCurrentProjectState(null);
// Remove from cache and persist // Remove from cache and persist