This is an automated email from the ASF dual-hosted git repository.

EnxDev pushed a commit to branch test/chatbot-local
in repository https://gitbox.apache.org/repos/asf/superset.git

commit a74684b06250087261eb65e1dbdc7fb13969133f
Author: Enzo Martellucci <[email protected]>
AuthorDate: Wed May 27 12:10:16 2026 +0200

    fix(extensions): port CodeAnt fixes — page-type guards, deactivation 
cleanup, settings race, feature flag gate
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
 superset-frontend/src/core/dashboard/index.ts      |  2 ++
 superset-frontend/src/core/explore/index.ts        |  2 ++
 .../src/extensions/ExtensionsList.tsx              | 19 ++++++++-----
 .../src/extensions/ExtensionsLoader.ts             | 31 +++++++++++++++++-----
 superset-frontend/src/views/App.tsx                |  3 ++-
 5 files changed, 43 insertions(+), 14 deletions(-)

diff --git a/superset-frontend/src/core/dashboard/index.ts 
b/superset-frontend/src/core/dashboard/index.ts
index fbf17f3fa62..611a4514f0b 100644
--- a/superset-frontend/src/core/dashboard/index.ts
+++ b/superset-frontend/src/core/dashboard/index.ts
@@ -34,11 +34,13 @@ import {
 import { store, RootState } from 'src/views/store';
 import { AnyListenerPredicate } from '@reduxjs/toolkit';
 import { createActionListener } from '../utils';
+import { navigation } from '../navigation';
 
 type DashboardContext = dashboardApi.DashboardContext;
 type FilterValue = dashboardApi.FilterValue;
 
 function buildDashboardContext(): DashboardContext | undefined {
+  if (navigation.getPageType() !== 'dashboard') return undefined;
   const state = store.getState();
   const info = (state as any).dashboardInfo;
   if (!info?.id) return undefined;
diff --git a/superset-frontend/src/core/explore/index.ts 
b/superset-frontend/src/core/explore/index.ts
index 46b6b2ef84e..732ae975caa 100644
--- a/superset-frontend/src/core/explore/index.ts
+++ b/superset-frontend/src/core/explore/index.ts
@@ -34,10 +34,12 @@ import { SET_DATASOURCE } from 
'src/explore/actions/datasourcesActions';
 import { store, RootState } from 'src/views/store';
 import { AnyListenerPredicate } from '@reduxjs/toolkit';
 import { createActionListener } from '../utils';
+import { navigation } from '../navigation';
 
 type ChartContext = exploreApi.ChartContext;
 
 function buildChartContext(): ChartContext | undefined {
+  if (navigation.getPageType() !== 'explore') return undefined;
   const state = store.getState();
   const exploreState = (state as any).explore;
   if (!exploreState) return undefined;
diff --git a/superset-frontend/src/extensions/ExtensionsList.tsx 
b/superset-frontend/src/extensions/ExtensionsList.tsx
index 5daceb60dc2..f57cefba970 100644
--- a/superset-frontend/src/extensions/ExtensionsList.tsx
+++ b/superset-frontend/src/extensions/ExtensionsList.tsx
@@ -79,6 +79,10 @@ const ExtensionsList: FunctionComponent<ExtensionsListProps> 
= ({
     active_chatbot_id: null,
     enabled: {},
   });
+  // Always holds the latest settings value so saveSettings never closes over
+  // a stale snapshot when rapid toggles race against each other.
+  const settingsRef = useRef(settings);
+  settingsRef.current = settings;
 
   const [chatbotRegistryVersion, setChatbotRegistryVersion] = useState(0);
   useEffect(
@@ -97,7 +101,8 @@ const ExtensionsList: FunctionComponent<ExtensionsListProps> 
= ({
 
   const saveSettings = useCallback(
     (patch: Partial<ExtensionSettings>) => {
-      const next = { ...settings, ...patch };
+      const next = { ...settingsRef.current, ...patch };
+      setSettings(next);
       SupersetClient.put({
         endpoint: '/api/v1/extensions/settings',
         jsonPayload: next,
@@ -109,25 +114,27 @@ const ExtensionsList: 
FunctionComponent<ExtensionsListProps> = ({
         })
         .catch(() => addDangerToast(t('Failed to save extension settings.')));
     },
-    [settings, addDangerToast, addSuccessToast],
+    [addDangerToast, addSuccessToast],
   );
 
   const toggleEnabled = useCallback(
     (extensionId: string, enabled: boolean) => {
       saveSettings({
-        enabled: { ...settings.enabled, [extensionId]: enabled },
+        enabled: { ...settingsRef.current.enabled, [extensionId]: enabled },
       });
     },
-    [settings, saveSettings],
+    [saveSettings],
   );
 
   const setDefaultChatbot = useCallback(
     (extensionId: string) => {
       const next =
-        settings.active_chatbot_id === extensionId ? null : extensionId;
+        settingsRef.current.active_chatbot_id === extensionId
+          ? null
+          : extensionId;
       saveSettings({ active_chatbot_id: next });
     },
-    [settings, saveSettings],
+    [saveSettings],
   );
 
   const chatbotIds = useMemo(
diff --git a/superset-frontend/src/extensions/ExtensionsLoader.ts 
b/superset-frontend/src/extensions/ExtensionsLoader.ts
index cd99a27dce2..f96091e9d2b 100644
--- a/superset-frontend/src/extensions/ExtensionsLoader.ts
+++ b/superset-frontend/src/extensions/ExtensionsLoader.ts
@@ -177,17 +177,34 @@ class ExtensionsLoader {
     // collect the Disposables and drive cleanup on deactivation.
     const collected: (() => void)[] = [];
     const originalSuperset = window.superset;
+
+    const wrap =
+      <TArgs extends unknown[]>(
+        fn: (...args: TArgs) => { dispose(): void },
+      ): ((...args: TArgs) => { dispose(): void }) =>
+      (...args: TArgs) => {
+        const disposable = fn(...args);
+        collected.push(() => disposable.dispose());
+        return disposable;
+      };
+
     window.superset = {
       ...originalSuperset,
+      commands: {
+        ...originalSuperset.commands,
+        registerCommand: wrap(originalSuperset.commands.registerCommand),
+      },
+      menus: {
+        ...originalSuperset.menus,
+        registerMenuItem: wrap(originalSuperset.menus.registerMenuItem),
+      },
+      editors: {
+        ...originalSuperset.editors,
+        registerEditor: wrap(originalSuperset.editors.registerEditor),
+      },
       views: {
         ...originalSuperset.views,
-        registerView: (
-          ...args: Parameters<typeof originalSuperset.views.registerView>
-        ) => {
-          const disposable = originalSuperset.views.registerView(...args);
-          collected.push(() => disposable.dispose());
-          return disposable;
-        },
+        registerView: wrap(originalSuperset.views.registerView),
       },
     };
 
diff --git a/superset-frontend/src/views/App.tsx 
b/superset-frontend/src/views/App.tsx
index d84b111ca84..d70aa0f86f9 100644
--- a/superset-frontend/src/views/App.tsx
+++ b/superset-frontend/src/views/App.tsx
@@ -38,6 +38,7 @@ import { Logger, LOG_ACTIONS_SPA_NAVIGATION } from 
'src/logger/LogUtils';
 import setupCodeOverrides from 'src/setup/setupCodeOverrides';
 import { logEvent } from 'src/logger/actions';
 import { store } from 'src/views/store';
+import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
 import ExtensionsStartup from 'src/extensions/ExtensionsStartup';
 import ChatbotMount from 'src/components/ChatbotMount';
 import { RootContextProviders } from './RootContextProviders';
@@ -119,7 +120,7 @@ const App = () => (
           loaded and registered, but outside the Switch so the bubble persists
           across route changes (SIP §3.2).
         */}
-        <ChatbotMount />
+        {isFeatureEnabled(FeatureFlag.EnableExtensions) && <ChatbotMount />}
       </ExtensionsStartup>
       <ToastContainer />
     </RootContextProviders>

Reply via email to