fix(FN-XXX): keep experimental views off by default
This commit is contained in:
5
.changeset/fix-experimental-defaults.md
Normal file
5
.changeset/fix-experimental-defaults.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Keep experimental dashboard views off by default until project settings enable them.
|
||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user