fix(FN-XXX): keep experimental views off by default

This commit is contained in:
gsxdsm
2026-04-29 16:11:59 -07:00
parent d8baa7a6fd
commit 118a03a34d
5 changed files with 73 additions and 6 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Keep experimental dashboard views off by default until project settings enable them.

View File

@@ -354,6 +354,7 @@ function AppInner() {
taskStuckTimeoutMs, taskStuckTimeoutMs,
showQuickChatFAB, showQuickChatFAB,
prAuthAvailable, prAuthAvailable,
settingsLoaded,
experimentalFeatures, experimentalFeatures,
insightsEnabled, insightsEnabled,
roadmapEnabled, roadmapEnabled,
@@ -371,9 +372,11 @@ function AppInner() {
const agentsEnabled = true; const agentsEnabled = true;
// Redirect to board if feature-gated views are disabled. // Redirect to board if feature-gated views are disabled.
// Only run after settings have been loaded (experimentalFeatures is non-empty)
useEffect(() => { useEffect(() => {
if (Object.keys(experimentalFeatures).length === 0) return; if (!settingsLoaded) return;
if (taskView === "skills" && !skillsEnabled) {
handleChangeTaskView("board");
}
if (taskView === "insights" && !insightsEnabled) { if (taskView === "insights" && !insightsEnabled) {
handleChangeTaskView("board"); handleChangeTaskView("board");
} }
@@ -392,7 +395,7 @@ function AppInner() {
if (taskView === "todos" && !todosEnabled) { if (taskView === "todos" && !todosEnabled) {
handleChangeTaskView("board"); handleChangeTaskView("board");
} }
}, [taskView, insightsEnabled, roadmapEnabled, experimentalFeatures, handleChangeTaskView, agentsEnabled, memoryEnabled, devServerEnabled, todosEnabled]); }, [taskView, settingsLoaded, skillsEnabled, insightsEnabled, roadmapEnabled, handleChangeTaskView, agentsEnabled, memoryEnabled, devServerEnabled, todosEnabled]);
// Auto-close nodes overlay if feature flag is toggled off while overlay is open // Auto-close nodes overlay if feature flag is toggled off while overlay is open
useEffect(() => { useEffect(() => {
@@ -595,9 +598,7 @@ function AppInner() {
// Project view // Project view
if (taskView === "skills") { if (taskView === "skills") {
if (!skillsEnabled) { if (!settingsLoaded || !skillsEnabled) {
// Redirect to board if skills view is not enabled
handleChangeTaskView("board");
return null; return null;
} }
return ( return (
@@ -636,6 +637,9 @@ function AppInner() {
} }
if (taskView === "roadmaps") { if (taskView === "roadmaps") {
if (!settingsLoaded || !roadmapEnabled) {
return null;
}
return ( return (
<PageErrorBoundary> <PageErrorBoundary>
<Suspense fallback={null}> <Suspense fallback={null}>
@@ -698,6 +702,9 @@ function AppInner() {
} }
if (taskView === "insights") { if (taskView === "insights") {
if (!settingsLoaded || !insightsEnabled) {
return null;
}
return ( return (
<PageErrorBoundary> <PageErrorBoundary>
<Suspense fallback={null}> <Suspense fallback={null}>
@@ -712,6 +719,9 @@ function AppInner() {
} }
if (taskView === "memory") { if (taskView === "memory") {
if (!settingsLoaded || !memoryEnabled) {
return null;
}
return ( return (
<PageErrorBoundary> <PageErrorBoundary>
<Suspense fallback={null}> <Suspense fallback={null}>
@@ -722,6 +732,9 @@ function AppInner() {
} }
if (taskView === "todos") { if (taskView === "todos") {
if (!settingsLoaded || !todosEnabled) {
return null;
}
return ( return (
<PageErrorBoundary> <PageErrorBoundary>
<Suspense fallback={null}> <Suspense fallback={null}>
@@ -732,6 +745,9 @@ function AppInner() {
} }
if (taskView === "devserver" || taskView === "dev-server") { if (taskView === "devserver" || taskView === "dev-server") {
if (!settingsLoaded || !devServerEnabled) {
return null;
}
return ( return (
<PageErrorBoundary> <PageErrorBoundary>
<Suspense fallback={null}> <Suspense fallback={null}>

View File

@@ -1664,6 +1664,39 @@ describe("App view switching", () => {
expect(screen.queryByTestId("view-overflow-insights")).toBeNull(); expect(screen.queryByTestId("view-overflow-insights")).toBeNull();
}); });
it("keeps experimental views off until settings load and falls back to board when no flag is enabled", async () => {
localStorage.setItem(taskViewStorageKey(), "insights");
let resolveSettings: ((settings: Settings) => void) | undefined;
vi.mocked(fetchSettings).mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveSettings = resolve as (settings: Settings) => void;
}),
);
render(<App />);
await waitFor(() => {
expect(screen.getByTitle("Board view")).toBeTruthy();
});
expect(document.querySelector(".insights-view")).toBeNull();
expect(document.querySelector(".board")).toBeNull();
resolveSettings?.({
...defaultSettings,
experimentalFeatures: {},
});
await waitFor(() => {
expect(document.querySelector(".board")).toBeTruthy();
});
expect(document.querySelector(".insights-view")).toBeNull();
localStorage.removeItem(taskViewStorageKey());
});
it("does not render memory view button when memoryView experimental feature is disabled", async () => { it("does not render memory view button when memoryView experimental feature is disabled", async () => {
// Keep another overflow item enabled so the overflow trigger still renders. // Keep another overflow item enabled so the overflow trigger still renders.
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ (fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({

View File

@@ -44,6 +44,7 @@ describe("useAppSettings", () => {
expect(result.current.globalPaused).toBe(true); expect(result.current.globalPaused).toBe(true);
expect(result.current.enginePaused).toBe(false); expect(result.current.enginePaused).toBe(false);
expect(result.current.prAuthAvailable).toBe(true); expect(result.current.prAuthAvailable).toBe(true);
expect(result.current.settingsLoaded).toBe(true);
expect(result.current.taskStuckTimeoutMs).toBe(600000); expect(result.current.taskStuckTimeoutMs).toBe(600000);
expect(result.current.showQuickChatFAB).toBe(false); expect(result.current.showQuickChatFAB).toBe(false);
}); });

View File

@@ -13,6 +13,7 @@ export interface UseAppSettingsResult {
taskStuckTimeoutMs: number | undefined; taskStuckTimeoutMs: number | undefined;
showQuickChatFAB: boolean; showQuickChatFAB: boolean;
prAuthAvailable: boolean; prAuthAvailable: boolean;
settingsLoaded: boolean;
experimentalFeatures: Record<string, boolean>; experimentalFeatures: Record<string, boolean>;
insightsEnabled: boolean; insightsEnabled: boolean;
roadmapEnabled: boolean; roadmapEnabled: boolean;
@@ -39,6 +40,7 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
const [taskStuckTimeoutMs, setTaskStuckTimeoutMs] = useState<number | undefined>(undefined); const [taskStuckTimeoutMs, setTaskStuckTimeoutMs] = useState<number | undefined>(undefined);
const [showQuickChatFAB, setShowQuickChatFAB] = useState(false); const [showQuickChatFAB, setShowQuickChatFAB] = useState(false);
const [prAuthAvailable, setPrAuthAvailable] = useState(false); const [prAuthAvailable, setPrAuthAvailable] = useState(false);
const [settingsLoaded, setSettingsLoaded] = useState(false);
const [experimentalFeatures, setExperimentalFeatures] = useState<Record<string, boolean>>({}); const [experimentalFeatures, setExperimentalFeatures] = useState<Record<string, boolean>>({});
const [insightsEnabled, setInsightsEnabled] = useState(false); const [insightsEnabled, setInsightsEnabled] = useState(false);
const [roadmapEnabled, setRoadmapEnabled] = useState(false); const [roadmapEnabled, setRoadmapEnabled] = useState(false);
@@ -77,9 +79,18 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
setDevServerEnabled(features.devServerView === true || features.devServer === true); setDevServerEnabled(features.devServerView === true || features.devServer === true);
setTodosEnabled(features.todoView === true); setTodosEnabled(features.todoView === true);
} }
setSettingsLoaded(true);
}, [projectId]); }, [projectId]);
useEffect(() => { useEffect(() => {
setSettingsLoaded(false);
setExperimentalFeatures({});
setInsightsEnabled(false);
setRoadmapEnabled(false);
setMemoryEnabled(false);
setDevServerEnabled(false);
setTodosEnabled(false);
void refresh(); void refresh();
}, [refresh]); }, [refresh]);
@@ -142,6 +153,7 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
taskStuckTimeoutMs, taskStuckTimeoutMs,
showQuickChatFAB, showQuickChatFAB,
prAuthAvailable, prAuthAvailable,
settingsLoaded,
experimentalFeatures, experimentalFeatures,
insightsEnabled, insightsEnabled,
roadmapEnabled, roadmapEnabled,