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

View File

@@ -1664,6 +1664,39 @@ describe("App view switching", () => {
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 () => {
// Keep another overflow item enabled so the overflow trigger still renders.
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({

View File

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

View File

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