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

kgabryje pushed a commit to branch what-if
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 888e14eb0c159b6991a4b783a4d03b3d0590936c
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Thu Dec 18 23:29:22 2025 +0100

    Code cleanup + HARRY POTTER
---
 .../dashboard/components/WhatIfBanner/index.tsx    |  31 +-
 .../components/WhatIfDrawer/FilterSection.tsx      | 153 ++++
 .../WhatIfDrawer/HarryPotterWandLoader.tsx         | 493 +++++++++++++
 .../WhatIfDrawer/ModificationsDisplay.tsx          | 117 +++
 .../components/WhatIfDrawer/WhatIfAIInsights.tsx   |  47 +-
 .../dashboard/components/WhatIfDrawer/constants.ts |  38 +
 .../dashboard/components/WhatIfDrawer/index.tsx    | 799 +++------------------
 .../dashboard/components/WhatIfDrawer/styles.ts    | 207 ++++++
 .../src/dashboard/components/WhatIfDrawer/types.ts |  32 +-
 .../components/WhatIfDrawer/useChartComparison.ts  |  25 +-
 .../components/WhatIfDrawer/useWhatIfApply.ts      | 233 ++++++
 .../components/WhatIfDrawer/useWhatIfFilters.ts    | 227 ++++++
 superset-frontend/src/dashboard/types.ts           |   1 +
 .../src/dashboard/util/useNumericColumns.ts        |  59 ++
 superset-frontend/src/dashboard/util/whatIf.ts     |  68 +-
 15 files changed, 1729 insertions(+), 801 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/WhatIfBanner/index.tsx 
b/superset-frontend/src/dashboard/components/WhatIfBanner/index.tsx
index 2eca2d7b90..4fd5a6a546 100644
--- a/superset-frontend/src/dashboard/components/WhatIfBanner/index.tsx
+++ b/superset-frontend/src/dashboard/components/WhatIfBanner/index.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
 import { t } from '@superset-ui/core';
 import { styled, useTheme } from '@apache-superset/core/ui';
@@ -24,8 +24,9 @@ import { Button } from '@superset-ui/core/components';
 import { Icons } from '@superset-ui/core/components/Icons';
 import { clearWhatIfModifications } from 
'src/dashboard/actions/dashboardState';
 import { restoreOriginalChartData } from 'src/components/Chart/chartAction';
-import { getNumericColumnsForDashboard } from 'src/dashboard/util/whatIf';
-import { RootState, Slice, WhatIfModification } from 'src/dashboard/types';
+import { formatPercentageChange } from 'src/dashboard/util/whatIf';
+import { useNumericColumns } from 'src/dashboard/util/useNumericColumns';
+import { RootState, WhatIfModification } from 'src/dashboard/types';
 
 const EMPTY_MODIFICATIONS: WhatIfModification[] = [];
 
@@ -83,12 +84,6 @@ const ExitButton = styled(Button)`
   }
 `;
 
-const formatPercentageChange = (multiplier: number): string => {
-  const percentChange = (multiplier - 1) * 100;
-  const sign = percentChange >= 0 ? '+' : '';
-  return `${sign}${Math.round(percentChange)}%`;
-};
-
 interface WhatIfBannerProps {
   topOffset: number;
 }
@@ -101,23 +96,7 @@ const WhatIfBanner = ({ topOffset }: WhatIfBannerProps) => {
     state => state.dashboardState.whatIfModifications ?? EMPTY_MODIFICATIONS,
   );
 
-  const slices = useSelector(
-    (state: RootState) => state.sliceEntities.slices as { [id: number]: Slice 
},
-  );
-  const datasources = useSelector((state: RootState) => state.datasources);
-
-  const numericColumns = useMemo(
-    () => getNumericColumnsForDashboard(slices, datasources),
-    [slices, datasources],
-  );
-
-  const columnToChartIds = useMemo(() => {
-    const map = new Map<string, number[]>();
-    numericColumns.forEach(col => {
-      map.set(col.columnName, col.usedByChartIds);
-    });
-    return map;
-  }, [numericColumns]);
+  const { columnToChartIds } = useNumericColumns();
 
   const handleExitWhatIf = useCallback(() => {
     const affectedChartIds = new Set<number>();
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/FilterSection.tsx 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/FilterSection.tsx
new file mode 100644
index 0000000000..16ad32555f
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/WhatIfDrawer/FilterSection.tsx
@@ -0,0 +1,153 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { memo } from 'react';
+import { t } from '@superset-ui/core';
+import { css, useTheme } from '@apache-superset/core/ui';
+import { Tooltip, Tag, Popover } from '@superset-ui/core/components';
+import { Icons } from '@superset-ui/core/components/Icons';
+import { WhatIfFilter, Datasource } from 'src/dashboard/types';
+import AdhocFilter from 
'src/explore/components/controls/FilterControl/AdhocFilter';
+import AdhocFilterEditPopover from 
'src/explore/components/controls/FilterControl/AdhocFilterEditPopover';
+import {
+  FilterButton,
+  FilterPopoverContent,
+  FiltersSection,
+  FilterTagsContainer,
+  Label,
+} from './styles';
+
+interface FilterSectionProps {
+  filters: WhatIfFilter[];
+  filterPopoverVisible: boolean;
+  currentAdhocFilter: AdhocFilter | null;
+  selectedColumn: string | undefined;
+  selectedDatasource: Datasource | null;
+  filterColumnOptions: Datasource['columns'];
+  onOpenFilterPopover: () => void;
+  onFilterPopoverVisibleChange: (visible: boolean) => void;
+  onFilterChange: (adhocFilter: AdhocFilter) => void;
+  onFilterPopoverClose: () => void;
+  onFilterPopoverResize: () => void;
+  onEditFilter: (index: number) => void;
+  onRemoveFilter: (e: React.MouseEvent, index: number) => void;
+  formatFilterLabel: (filter: WhatIfFilter) => string;
+}
+
+/**
+ * Component for rendering the filter button and filter tags.
+ * Uses memo to prevent unnecessary re-renders when parent state changes
+ * that don't affect this component.
+ */
+const FilterSection = memo(function FilterSection({
+  filters,
+  filterPopoverVisible,
+  currentAdhocFilter,
+  selectedColumn,
+  selectedDatasource,
+  filterColumnOptions,
+  onOpenFilterPopover,
+  onFilterPopoverVisibleChange,
+  onFilterChange,
+  onFilterPopoverClose,
+  onFilterPopoverResize,
+  onEditFilter,
+  onRemoveFilter,
+  formatFilterLabel,
+}: FilterSectionProps) {
+  const theme = useTheme();
+
+  return (
+    <>
+      <Popover
+        open={filterPopoverVisible}
+        onOpenChange={onFilterPopoverVisibleChange}
+        trigger="click"
+        placement="left"
+        destroyOnHidden
+        content={
+          currentAdhocFilter && selectedDatasource ? (
+            <FilterPopoverContent>
+              <AdhocFilterEditPopover
+                adhocFilter={currentAdhocFilter}
+                options={filterColumnOptions}
+                datasource={selectedDatasource}
+                onChange={onFilterChange}
+                onClose={onFilterPopoverClose}
+                onResize={onFilterPopoverResize}
+                requireSave
+              />
+            </FilterPopoverContent>
+          ) : null
+        }
+      >
+        <Tooltip
+          title={
+            selectedColumn
+              ? t('Add filter to scope the modification')
+              : t('Select a column first')
+          }
+        >
+          <FilterButton
+            onClick={onOpenFilterPopover}
+            disabled={!selectedColumn || !selectedDatasource}
+            aria-label={t('Add filter')}
+            buttonStyle="tertiary"
+          >
+            <Icons.FilterOutlined iconSize="m" />
+          </FilterButton>
+        </Tooltip>
+      </Popover>
+
+      {filters.length > 0 && (
+        <FiltersSection>
+          <Label
+            css={css`
+              font-size: ${theme.fontSizeSM}px;
+              color: ${theme.colorTextSecondary};
+            `}
+          >
+            {t('Filters')}
+          </Label>
+          <FilterTagsContainer>
+            {filters.map((filter, index) => (
+              <Tag
+                key={`${filter.col}-${filter.op}-${index}`}
+                closable
+                onClose={e => onRemoveFilter(e, index)}
+                onClick={() => onEditFilter(index)}
+                css={css`
+                  cursor: pointer;
+                  margin: 0;
+                  &:hover {
+                    opacity: 0.8;
+                  }
+                `}
+              >
+                {formatFilterLabel(filter)}
+              </Tag>
+            ))}
+          </FilterTagsContainer>
+        </FiltersSection>
+      )}
+    </>
+  );
+});
+
+export default FilterSection;
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/HarryPotterWandLoader.tsx
 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/HarryPotterWandLoader.tsx
new file mode 100644
index 0000000000..f7dcbde50c
--- /dev/null
+++ 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/HarryPotterWandLoader.tsx
@@ -0,0 +1,493 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useEffect } from 'react';
+import { createPortal } from 'react-dom';
+import { keyframes } from '@emotion/react';
+import { styled } from '@apache-superset/core/ui';
+import { t } from '@superset-ui/core';
+
+// eslint-disable theme-colors/no-literal-colors
+
+// Casting spell motion - dramatic swish and flick!
+const castSpell = keyframes`
+  0% {
+    transform: rotate(-30deg) translateY(0);
+  }
+  15% {
+    transform: rotate(-45deg) translateY(-5px);
+  }
+  30% {
+    transform: rotate(25deg) translateY(0);
+  }
+  45% {
+    transform: rotate(15deg) translateY(-3px);
+  }
+  60% {
+    transform: rotate(-10deg) translateY(0);
+  }
+  75% {
+    transform: rotate(5deg) translateY(-2px);
+  }
+  100% {
+    transform: rotate(-30deg) translateY(0);
+  }
+`;
+
+// Magic particles floating upward
+const floatUp = keyframes`
+  0% {
+    opacity: 1;
+    transform: translateY(0) translateX(0) scale(1);
+  }
+  100% {
+    opacity: 0;
+    transform: translateY(-100px) translateX(var(--drift-x, 0px)) scale(0);
+  }
+`;
+
+// Spiral magic effect
+const spiral = keyframes`
+  0% {
+    transform: rotate(0deg) translateX(20px) rotate(0deg);
+    opacity: 1;
+  }
+  100% {
+    transform: rotate(720deg) translateX(80px) rotate(-720deg);
+    opacity: 0;
+  }
+`;
+
+// Glowing tip pulse
+const glowPulse = keyframes`
+  0%, 100% {
+    filter: drop-shadow(0 0 8px #fff) drop-shadow(0 0 15px #87CEEB) 
drop-shadow(0 0 25px #4169E1);
+    opacity: 0.9;
+  }
+  50% {
+    filter: drop-shadow(0 0 15px #fff) drop-shadow(0 0 30px #87CEEB) 
drop-shadow(0 0 45px #4169E1);
+    opacity: 1;
+  }
+`;
+
+// Stars twinkling
+const twinkle = keyframes`
+  0%, 100% {
+    opacity: 0;
+    transform: scale(0) rotate(0deg);
+  }
+  25% {
+    opacity: 1;
+    transform: scale(1) rotate(90deg);
+  }
+  50% {
+    opacity: 0.5;
+    transform: scale(0.8) rotate(180deg);
+  }
+  75% {
+    opacity: 1;
+    transform: scale(1.2) rotate(270deg);
+  }
+`;
+
+// Lumos light burst
+const lumosBurst = keyframes`
+  0% {
+    transform: scale(0);
+    opacity: 0;
+  }
+  20% {
+    transform: scale(1.5);
+    opacity: 0.8;
+  }
+  100% {
+    transform: scale(3);
+    opacity: 0;
+  }
+`;
+
+const fadeIn = keyframes`
+  from { opacity: 0; }
+  to { opacity: 1; }
+`;
+
+const textGlow = keyframes`
+  0%, 100% {
+    text-shadow: 0 0 10px #87CEEB, 0 0 20px #4169E1, 0 0 30px #4169E1;
+  }
+  50% {
+    text-shadow: 0 0 20px #87CEEB, 0 0 40px #4169E1, 0 0 60px #4169E1, 0 0 
80px #6495ED;
+  }
+`;
+
+/* eslint-disable theme-colors/no-literal-colors */
+const Overlay = styled.div`
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: radial-gradient(ellipse at center, rgba(26, 26, 46, 0.25) 0%, 
rgba(13, 13, 26, 0.28) 50%, rgba(0, 0, 5, 0.3) 100%);
+  backdrop-filter: blur(3px);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  z-index: 99999;
+  animation: ${fadeIn} 0.5s ease-out;
+  overflow: hidden;
+`;
+
+const StarsBackground = styled.div`
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  overflow: hidden;
+`;
+
+const Star = styled.div<{ size: number; x: number; y: number; delay: number }>`
+  position: absolute;
+  width: ${({ size }) => size}px;
+  height: ${({ size }) => size}px;
+  left: ${({ x }) => x}%;
+  top: ${({ y }) => y}%;
+  background: radial-gradient(circle, #fff 0%, transparent 70%);
+  border-radius: 50%;
+  animation: ${twinkle} ${({ delay }) => 2 + delay}s ease-in-out infinite;
+  animation-delay: ${({ delay }) => delay}s;
+`;
+
+const WandScene = styled.div`
+  position: relative;
+  width: 300px;
+  height: 300px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+const WandWrapper = styled.div`
+  position: relative;
+  animation: ${castSpell} 2.5s ease-in-out infinite;
+  transform-origin: 85% 85%;
+`;
+
+const WandSvg = styled.svg`
+  width: 180px;
+  height: 180px;
+  transform: rotate(-45deg);
+`;
+
+const WandTipGlow = styled.div`
+  position: absolute;
+  top: -5px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 20px;
+  height: 20px;
+  background: radial-gradient(circle, #fff 0%, #87CEEB 30%, #4169E1 60%, 
transparent 80%);
+  border-radius: 50%;
+  animation: ${glowPulse} 1s ease-in-out infinite;
+`;
+
+const LumosBurst = styled.div`
+  position: absolute;
+  top: -10px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 40px;
+  height: 40px;
+  background: radial-gradient(circle, rgba(255,255,255,0.8) 0%, 
rgba(135,206,235,0.4) 40%, transparent 70%);
+  border-radius: 50%;
+  animation: ${lumosBurst} 2s ease-out infinite;
+`;
+
+const MagicParticle = styled.div<{ delay: number; driftX: number; duration: 
number }>`
+  position: absolute;
+  top: 20%;
+  left: 50%;
+  width: 6px;
+  height: 6px;
+  background: radial-gradient(circle, #fff 0%, #87CEEB 50%, transparent 100%);
+  border-radius: 50%;
+  --drift-x: ${({ driftX }) => driftX}px;
+  animation: ${floatUp} ${({ duration }) => duration}s ease-out infinite;
+  animation-delay: ${({ delay }) => delay}s;
+`;
+
+const SpiralMagic = styled.div<{ delay: number; color: string }>`
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 8px;
+  height: 8px;
+  background: ${({ color }) => color};
+  border-radius: 50%;
+  animation: ${spiral} 3s ease-out infinite;
+  animation-delay: ${({ delay }) => delay}s;
+  box-shadow: 0 0 10px ${({ color }) => color};
+`;
+
+const MagicStar = styled.div<{ x: number; y: number; delay: number; size: 
number }>`
+  position: absolute;
+  left: ${({ x }) => x}%;
+  top: ${({ y }) => y}%;
+  width: ${({ size }) => size}px;
+  height: ${({ size }) => size}px;
+  animation: ${twinkle} 1.5s ease-in-out infinite;
+  animation-delay: ${({ delay }) => delay}s;
+
+  &::before,
+  &::after {
+    content: '';
+    position: absolute;
+    background: linear-gradient(135deg, #fff 0%, #87CEEB 50%, #4169E1 100%);
+  }
+
+  &::before {
+    width: 100%;
+    height: 2px;
+    top: 50%;
+    left: 0;
+    transform: translateY(-50%);
+  }
+
+  &::after {
+    width: 2px;
+    height: 100%;
+    top: 0;
+    left: 50%;
+    transform: translateX(-50%);
+  }
+`;
+
+const SpellText = styled.div`
+  margin-top: 40px;
+  font-family: 'Georgia', 'Times New Roman', serif;
+  font-size: 28px;
+  font-style: italic;
+  color: #87CEEB;
+  animation: ${textGlow} 2s ease-in-out infinite;
+  letter-spacing: 3px;
+`;
+
+const SubText = styled.div`
+  margin-top: 16px;
+  font-size: 14px;
+  color: #6495ED;
+  opacity: 0.8;
+  letter-spacing: 1px;
+`;
+
+const DismissHint = styled.div`
+  margin-top: 32px;
+  font-size: 12px;
+  color: #4a4a6a;
+  opacity: 0.6;
+`;
+/* eslint-enable theme-colors/no-literal-colors */
+
+// Generate random stars for background
+const backgroundStars = Array.from({ length: 50 }, (_, i) => ({
+  id: i,
+  size: Math.random() * 3 + 1,
+  x: Math.random() * 100,
+  y: Math.random() * 100,
+  delay: Math.random() * 3,
+}));
+
+// Magic particles around wand tip
+const particles = Array.from({ length: 12 }, (_, i) => ({
+  id: i,
+  delay: i * 0.15,
+  driftX: (Math.random() - 0.5) * 60,
+  duration: 1.5 + Math.random() * 0.5,
+}));
+
+// Spiral magic colors
+/* eslint-disable theme-colors/no-literal-colors */
+const spiralColors = ['#87CEEB', '#4169E1', '#6495ED', '#B0C4DE', '#ADD8E6', 
'#fff'];
+/* eslint-enable theme-colors/no-literal-colors */
+
+const spirals = spiralColors.map((color, i) => ({
+  id: i,
+  delay: i * 0.5,
+  color,
+}));
+
+// Magic stars around the scene
+const magicStars = [
+  { x: 15, y: 20, delay: 0, size: 16 },
+  { x: 80, y: 25, delay: 0.3, size: 12 },
+  { x: 25, y: 70, delay: 0.6, size: 14 },
+  { x: 75, y: 65, delay: 0.9, size: 10 },
+  { x: 10, y: 45, delay: 0.2, size: 8 },
+  { x: 88, y: 50, delay: 0.5, size: 11 },
+  { x: 50, y: 10, delay: 0.4, size: 13 },
+  { x: 45, y: 85, delay: 0.7, size: 9 },
+];
+
+interface HarryPotterWandLoaderProps {
+  onDismiss?: () => void;
+}
+
+const HarryPotterWandLoader = ({ onDismiss }: HarryPotterWandLoaderProps) => {
+  useEffect(() => {
+    const handleKeyDown = (e: KeyboardEvent) => {
+      if (e.key === 'Escape' && onDismiss) {
+        onDismiss();
+      }
+    };
+
+    document.addEventListener('keydown', handleKeyDown);
+    return () => document.removeEventListener('keydown', handleKeyDown);
+  }, [onDismiss]);
+
+  const content = (
+    <Overlay data-test="harry-potter-wand-loader" onClick={onDismiss}>
+      <StarsBackground>
+        {backgroundStars.map(star => (
+          <Star
+            key={star.id}
+            size={star.size}
+            x={star.x}
+            y={star.y}
+            delay={star.delay}
+          />
+        ))}
+      </StarsBackground>
+
+      <WandScene>
+        {magicStars.map((star, i) => (
+          <MagicStar
+            key={i}
+            x={star.x}
+            y={star.y}
+            delay={star.delay}
+            size={star.size}
+          />
+        ))}
+
+        {spirals.map(s => (
+          <SpiralMagic key={s.id} delay={s.delay} color={s.color} />
+        ))}
+
+        <WandWrapper>
+          <LumosBurst />
+          <WandTipGlow />
+          {particles.map(p => (
+            <MagicParticle
+              key={p.id}
+              delay={p.delay}
+              driftX={p.driftX}
+              duration={p.duration}
+            />
+          ))}
+
+          <WandSvg viewBox="0 0 100 100" fill="none">
+            <defs>
+              {/* Wood grain gradient for authentic wand look */}
+              <linearGradient id="hpWandWood" x1="0%" y1="0%" x2="100%" 
y2="0%">
+                <stop offset="0%" stopColor="#2C1810" />
+                <stop offset="20%" stopColor="#4A2C1A" />
+                <stop offset="40%" stopColor="#3D2314" />
+                <stop offset="60%" stopColor="#5C3A22" />
+                <stop offset="80%" stopColor="#3D2314" />
+                <stop offset="100%" stopColor="#2C1810" />
+              </linearGradient>
+
+              {/* Handle gradient - darker, more ornate */}
+              <linearGradient id="hpWandHandle" x1="0%" y1="0%" x2="100%" 
y2="0%">
+                <stop offset="0%" stopColor="#1A0F0A" />
+                <stop offset="30%" stopColor="#2C1810" />
+                <stop offset="50%" stopColor="#3D2314" />
+                <stop offset="70%" stopColor="#2C1810" />
+                <stop offset="100%" stopColor="#1A0F0A" />
+              </linearGradient>
+
+              {/* Glowing tip */}
+              <radialGradient id="hpWandGlow" cx="50%" cy="50%" r="50%">
+                <stop offset="0%" stopColor="#fff" />
+                <stop offset="40%" stopColor="#87CEEB" />
+                <stop offset="100%" stopColor="#4169E1" stopOpacity="0" />
+              </radialGradient>
+            </defs>
+
+            {/* Wand body - tapered shape like Elder Wand style */}
+            <path
+              d="M 50 10
+                 Q 52 15 52 20
+                 L 53 45
+                 Q 54 55 55 65
+                 L 57 80
+                 Q 58 85 56 88
+                 L 54 90
+                 Q 50 92 46 90
+                 L 44 88
+                 Q 42 85 43 80
+                 L 45 65
+                 Q 46 55 47 45
+                 L 48 20
+                 Q 48 15 50 10
+                 Z"
+              fill="url(#hpWandWood)"
+            />
+
+            {/* Handle bumps - Elder Wand style nodules */}
+            <ellipse cx="50" cy="75" rx="6" ry="3" fill="url(#hpWandHandle)" />
+            <ellipse cx="50" cy="82" rx="5" ry="2.5" fill="url(#hpWandHandle)" 
/>
+            <ellipse cx="50" cy="88" rx="4" ry="2" fill="url(#hpWandHandle)" />
+
+            {/* Wood grain lines */}
+            <path
+              d="M 49 25 Q 50 40 49 55"
+              stroke="#1A0F0A"
+              strokeWidth="0.5"
+              fill="none"
+              opacity="0.3"
+            />
+            <path
+              d="M 51 30 Q 52 45 51 60"
+              stroke="#1A0F0A"
+              strokeWidth="0.5"
+              fill="none"
+              opacity="0.3"
+            />
+
+            {/* Glowing tip */}
+            <circle cx="50" cy="8" r="6" fill="url(#hpWandGlow)" />
+            <circle cx="50" cy="8" r="3" fill="#fff" opacity="0.9" />
+          </WandSvg>
+        </WandWrapper>
+      </WandScene>
+
+      <SpellText>{t('Revelio...')}</SpellText>
+      <SubText>{t('Discovering hidden connections')}</SubText>
+      {onDismiss && (
+        <DismissHint>{t('Click anywhere or press Esc to cancel')}</DismissHint>
+      )}
+    </Overlay>
+  );
+
+  // Use portal to render at document.body level to escape any stacking 
context issues
+  return createPortal(content, document.body);
+};
+
+export default HarryPotterWandLoader;
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/ModificationsDisplay.tsx
 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/ModificationsDisplay.tsx
new file mode 100644
index 0000000000..44f19241d5
--- /dev/null
+++ 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/ModificationsDisplay.tsx
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { memo, useState, useCallback } from 'react';
+import { t } from '@superset-ui/core';
+import { css, useTheme } from '@apache-superset/core/ui';
+import { Tag } from '@superset-ui/core/components';
+import { Icons } from '@superset-ui/core/components/Icons';
+import { formatPercentageChange } from 'src/dashboard/util/whatIf';
+import { ExtendedWhatIfModification } from './types';
+import {
+  ModificationsSection,
+  ModificationTagsContainer,
+  AIBadge,
+  AIReasoningSection,
+  AIReasoningToggle,
+  AIReasoningContent,
+  AIReasoningItem,
+} from './styles';
+
+interface ModificationsDisplayProps {
+  modifications: ExtendedWhatIfModification[];
+}
+
+/**
+ * Component for displaying applied modifications as tags with AI reasoning.
+ * Uses memo to prevent unnecessary re-renders when modifications haven't 
changed.
+ */
+const ModificationsDisplay = memo(function ModificationsDisplay({
+  modifications,
+}: ModificationsDisplayProps) {
+  const theme = useTheme();
+  const [showAIReasoning, setShowAIReasoning] = useState(false);
+
+  const toggleAIReasoning = useCallback(() => {
+    setShowAIReasoning(prev => !prev);
+  }, []);
+
+  const hasAIReasoning = modifications.some(mod => mod.reasoning);
+
+  if (modifications.length === 0) {
+    return null;
+  }
+
+  return (
+    <ModificationsSection>
+      <ModificationTagsContainer>
+        {modifications.map((mod, idx) => (
+          <Tag
+            key={idx}
+            css={css`
+              display: inline-flex;
+              align-items: center;
+              gap: ${theme.sizeUnit}px;
+              margin: 0;
+            `}
+          >
+            <span>{mod.column}</span>
+            {mod.isAISuggested && <AIBadge>{t('AI')}</AIBadge>}
+            <span
+              css={css`
+                font-weight: ${theme.fontWeightStrong};
+                color: ${mod.multiplier >= 1
+                  ? theme.colorSuccess
+                  : theme.colorError};
+              `}
+            >
+              {formatPercentageChange(mod.multiplier, 1)}
+            </span>
+          </Tag>
+        ))}
+      </ModificationTagsContainer>
+
+      {hasAIReasoning && (
+        <AIReasoningSection>
+          <AIReasoningToggle onClick={toggleAIReasoning}>
+            {showAIReasoning ? (
+              <Icons.DownOutlined iconSize="xs" />
+            ) : (
+              <Icons.RightOutlined iconSize="xs" />
+            )}
+            {t('How AI chose these')}
+          </AIReasoningToggle>
+          {showAIReasoning && (
+            <AIReasoningContent>
+              {modifications
+                .filter(mod => mod.reasoning)
+                .map((mod, idx) => (
+                  <AIReasoningItem key={idx}>
+                    <strong>{mod.column}:</strong> {mod.reasoning}
+                  </AIReasoningItem>
+                ))}
+            </AIReasoningContent>
+          )}
+        </AIReasoningSection>
+      )}
+    </ModificationsSection>
+  );
+});
+
+export default ModificationsDisplay;
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/WhatIfAIInsights.tsx 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/WhatIfAIInsights.tsx
index 9f4e1616ee..dda919da61 100644
--- 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/WhatIfAIInsights.tsx
+++ 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/WhatIfAIInsights.tsx
@@ -33,6 +33,10 @@ import {
   WhatIfInterpretResponse,
 } from './types';
 
+// Static Skeleton paragraph configs to avoid recreation on each render
+const SKELETON_PARAGRAPH_3 = { rows: 3 };
+const SKELETON_PARAGRAPH_2 = { rows: 2 };
+
 /**
  * Create a stable key from modifications for comparison.
  * This allows us to detect when modifications have meaningfully changed.
@@ -150,49 +154,12 @@ const WhatIfAIInsights = ({
   const modificationsKey = getModificationsKey(modifications);
   const prevModificationsKeyRef = useRef<string>(modificationsKey);
 
-  // Debug logging for race condition diagnosis
-  const willTriggerFetch =
-    modifications.length > 0 &&
-    chartComparisons.length > 0 &&
-    allChartsLoaded &&
-    status === 'idle';
-
-  console.log('[WhatIfAIInsights] State:', {
-    affectedChartIds,
-    allChartsLoaded,
-    chartComparisonsLength: chartComparisons.length,
-    modificationsLength: modifications.length,
-    status,
-    modificationsKey,
-    willTriggerFetch,
-  });
-
-  // Log chart comparison details when about to fetch (helps diagnose race 
conditions)
-  if (willTriggerFetch && chartComparisons.length > 0) {
-    console.log(
-      '[WhatIfAIInsights] Chart comparisons to send:',
-      chartComparisons.map(c => ({
-        chartId: c.chartId,
-        chartName: c.chartName,
-        metrics: c.metrics.map(m => ({
-          name: m.metricName,
-          original: m.originalValue,
-          modified: m.modifiedValue,
-          change: `${m.percentageChange.toFixed(2)}%`,
-        })),
-      })),
-    );
-  }
-
   // Reset status when modifications change (user adjusts the slider)
   useEffect(() => {
     if (
       modificationsKey !== prevModificationsKeyRef.current &&
       modifications.length > 0
     ) {
-      console.log(
-        '[WhatIfAIInsights] Modifications changed, resetting status to idle',
-      );
       // Cancel any in-flight request when modifications change
       abortControllerRef.current?.abort();
       // eslint-disable-next-line react-hooks/set-state-in-effect -- 
Intentional: resetting state when modifications change
@@ -280,7 +247,9 @@ const WhatIfAIInsights = ({
         {t('AI Insights')}
       </InsightsHeader>
 
-      {status === 'loading' && <Skeleton active paragraph={{ rows: 3 }} />}
+      {status === 'loading' && (
+        <Skeleton active paragraph={SKELETON_PARAGRAPH_3} />
+      )}
 
       {status === 'error' && (
         <Alert
@@ -305,7 +274,7 @@ const WhatIfAIInsights = ({
       )}
 
       {status === 'idle' && !allChartsLoaded && (
-        <Skeleton active paragraph={{ rows: 2 }} />
+        <Skeleton active paragraph={SKELETON_PARAGRAPH_2} />
       )}
     </InsightsContainer>
   );
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/constants.ts 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/constants.ts
new file mode 100644
index 0000000000..02e6fa52c3
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/WhatIfDrawer/constants.ts
@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const WHAT_IF_PANEL_WIDTH = 340;
+
+export const SLIDER_MIN = -50;
+export const SLIDER_MAX = 50;
+export const SLIDER_DEFAULT = 0;
+
+// Static slider marks - defined at module level to avoid recreation
+export const SLIDER_MARKS: Record<number, string> = {
+  [SLIDER_MIN]: `${SLIDER_MIN}%`,
+  0: '0%',
+  [SLIDER_MAX]: `+${SLIDER_MAX}%`,
+};
+
+// Static tooltip formatter - defined at module level for stable reference
+export const sliderTooltipFormatter = (value?: number): string =>
+  value !== undefined ? `${value > 0 ? '+' : ''}${value}%` : '';
+
+// Memoized tooltip config object to prevent Slider re-renders
+export const SLIDER_TOOLTIP_CONFIG = { formatter: sliderTooltipFormatter };
diff --git a/superset-frontend/src/dashboard/components/WhatIfDrawer/index.tsx 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/index.tsx
index 9dee0ca6ae..7d00748bcf 100644
--- a/superset-frontend/src/dashboard/components/WhatIfDrawer/index.tsx
+++ b/superset-frontend/src/dashboard/components/WhatIfDrawer/index.tsx
@@ -16,232 +16,51 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { t, logging, formatTimeRangeLabel } from '@superset-ui/core';
-import { css, styled, Alert, useTheme } from '@apache-superset/core/ui';
+
+import { useCallback, useMemo, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { t } from '@superset-ui/core';
+import { css, Alert, useTheme } from '@apache-superset/core/ui';
 import {
-  Button,
   Select,
   Checkbox,
   Tooltip,
-  Tag,
-  Popover,
+  CheckboxChangeEvent,
 } from '@superset-ui/core/components';
 import Slider from '@superset-ui/core/components/Slider';
 import { Icons } from '@superset-ui/core/components/Icons';
-import { setWhatIfModifications } from 'src/dashboard/actions/dashboardState';
+import { useNumericColumns } from 'src/dashboard/util/useNumericColumns';
+import { RootState, Datasource } from 'src/dashboard/types';
+import WhatIfAIInsights from './WhatIfAIInsights';
+import HarryPotterWandLoader from './HarryPotterWandLoader';
+import FilterSection from './FilterSection';
+import ModificationsDisplay from './ModificationsDisplay';
+import { useWhatIfFilters } from './useWhatIfFilters';
+import { useWhatIfApply } from './useWhatIfApply';
 import {
-  triggerQuery,
-  saveOriginalChartData,
-} from 'src/components/Chart/chartAction';
-import { getNumericColumnsForDashboard } from 'src/dashboard/util/whatIf';
+  SLIDER_MIN,
+  SLIDER_MAX,
+  SLIDER_DEFAULT,
+  SLIDER_MARKS,
+  SLIDER_TOOLTIP_CONFIG,
+  WHAT_IF_PANEL_WIDTH,
+} from './constants';
 import {
-  RootState,
-  Slice,
-  WhatIfColumn,
-  WhatIfFilter,
-  Datasource,
-} from 'src/dashboard/types';
-import AdhocFilter from 
'src/explore/components/controls/FilterControl/AdhocFilter';
-import AdhocFilterEditPopover from 
'src/explore/components/controls/FilterControl/AdhocFilterEditPopover';
-import { Clauses } from 'src/explore/components/controls/FilterControl/types';
-import { OPERATOR_ENUM_TO_OPERATOR_TYPE } from 'src/explore/constants';
-import WhatIfAIInsights from './WhatIfAIInsights';
-import MagicWandLoader from './MagicWandLoader';
-import { fetchRelatedColumnSuggestions } from './whatIfApi';
-import { ExtendedWhatIfModification } from './types';
-
-export const WHAT_IF_PANEL_WIDTH = 340;
-
-const SLIDER_MIN = -50;
-const SLIDER_MAX = 50;
-const SLIDER_DEFAULT = 0;
-
-const PanelContainer = styled.div<{ topOffset: number }>`
-  grid-column: 2;
-  grid-row: 1 / -1; /* Span all rows */
-  width: ${WHAT_IF_PANEL_WIDTH}px;
-  min-width: ${WHAT_IF_PANEL_WIDTH}px;
-  background-color: ${({ theme }) => theme.colorBgContainer};
-  border-left: 1px solid ${({ theme }) => theme.colorBorderSecondary};
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
-  position: sticky;
-  top: ${({ topOffset }) => topOffset}px;
-  height: calc(100vh - ${({ topOffset }) => topOffset}px);
-  align-self: start;
-  z-index: 10;
-`;
-
-const PanelHeader = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: ${({ theme }) => theme.sizeUnit * 3}px
-    ${({ theme }) => theme.sizeUnit * 4}px;
-  border-bottom: 1px solid ${({ theme }) => theme.colorBorderSecondary};
-`;
-
-const PanelTitle = styled.div`
-  display: flex;
-  align-items: center;
-  gap: ${({ theme }) => theme.sizeUnit * 2}px;
-  font-weight: ${({ theme }) => theme.fontWeightStrong};
-  font-size: ${({ theme }) => theme.fontSizeLG}px;
-`;
-
-const CloseButton = styled.button`
-  background: none;
-  border: none;
-  cursor: pointer;
-  padding: ${({ theme }) => theme.sizeUnit}px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: ${({ theme }) => theme.colorTextSecondary};
-  border-radius: ${({ theme }) => theme.borderRadius}px;
-
-  &:hover {
-    background-color: ${({ theme }) => theme.colorBgTextHover};
-    color: ${({ theme }) => theme.colorText};
-  }
-`;
-
-const PanelContent = styled.div`
-  flex: 1;
-  overflow-y: auto;
-  padding: ${({ theme }) => theme.sizeUnit * 4}px;
-  display: flex;
-  flex-direction: column;
-  gap: ${({ theme }) => theme.sizeUnit * 5}px;
-`;
-
-const FormSection = styled.div`
-  display: flex;
-  flex-direction: column;
-  gap: ${({ theme }) => theme.sizeUnit * 2}px;
-`;
-
-const Label = styled.label`
-  color: ${({ theme }) => theme.colorText};
-`;
-
-const SliderContainer = styled.div`
-  padding: 0 ${({ theme }) => theme.sizeUnit}px;
-  & .ant-slider-mark {
-    font-size: ${({ theme }) => theme.fontSizeSM}px;
-  }
-`;
-
-const ApplyButton = styled(Button)`
-  width: 100%;
-  min-height: 32px;
-`;
-
-const CheckboxContainer = styled.div`
-  display: flex;
-  align-items: center;
-  gap: ${({ theme }) => theme.sizeUnit}px;
-`;
-
-const ModificationsSection = styled.div`
-  display: flex;
-  flex-direction: column;
-  gap: ${({ theme }) => theme.sizeUnit * 5}px;
-`;
-
-const ModificationTagsContainer = styled.div`
-  display: flex;
-  flex-wrap: wrap;
-  gap: ${({ theme }) => theme.sizeUnit * 2}px;
-`;
-
-const AIBadge = styled.span`
-  font-size: 10px;
-  padding: 0 4px;
-  background-color: ${({ theme }) => theme.colorInfo};
-  color: ${({ theme }) => theme.colorWhite};
-  border-radius: 16px;
-  line-height: 1.2;
-`;
-
-const AIReasoningSection = styled.div`
-  display: flex;
-  flex-direction: column;
-  gap: ${({ theme }) => theme.sizeUnit}px;
-`;
-
-const AIReasoningToggle = styled.button`
-  display: flex;
-  align-items: center;
-  gap: ${({ theme }) => theme.sizeUnit}px;
-  background: none;
-  border: none;
-  padding: 0;
-  cursor: pointer;
-  color: ${({ theme }) => theme.colorTextTertiary};
-  font-size: ${({ theme }) => theme.fontSizeSM}px;
-
-  &:hover {
-    color: ${({ theme }) => theme.colorText};
-  }
-`;
-
-const AIReasoningContent = styled.div`
-  display: flex;
-  flex-direction: column;
-  gap: ${({ theme }) => theme.sizeUnit}px;
-  padding-left: ${({ theme }) => theme.sizeUnit * 4}px;
-`;
-
-const AIReasoningItem = styled.div`
-  font-size: ${({ theme }) => theme.fontSizeSM}px;
-  color: ${({ theme }) => theme.colorTextSecondary};
-`;
-
-const ColumnSelectRow = styled.div`
-  display: flex;
-  gap: ${({ theme }) => theme.sizeUnit * 2}px;
-  align-items: flex-start;
-`;
-
-const ColumnSelectWrapper = styled.div`
-  flex: 1;
-  min-width: 0;
-`;
-
-const FilterButton = styled(Button)`
-  flex-shrink: 0;
-  padding: 0 ${({ theme }) => theme.sizeUnit * 2}px;
-`;
-
-const FilterPopoverContent = styled.div`
-  .edit-popover-resize {
-    transform: scaleX(-1);
-    float: right;
-    margin-top: ${({ theme }) => theme.sizeUnit * 4}px;
-    margin-right: ${({ theme }) => theme.sizeUnit * -1}px;
-    color: ${({ theme }) => theme.colorIcon};
-    cursor: nwse-resize;
-  }
-  .filter-sql-editor {
-    border: ${({ theme }) => theme.colorBorder} solid thin;
-  }
-`;
-
-const FiltersSection = styled.div`
-  display: flex;
-  flex-direction: column;
-  gap: ${({ theme }) => theme.sizeUnit * 2}px;
-`;
-
-const FilterTagsContainer = styled.div`
-  display: flex;
-  flex-wrap: wrap;
-  gap: ${({ theme }) => theme.sizeUnit}px;
-`;
+  PanelContainer,
+  PanelHeader,
+  PanelTitle,
+  CloseButton,
+  PanelContent,
+  FormSection,
+  Label,
+  SliderContainer,
+  ApplyButton,
+  CheckboxContainer,
+  ColumnSelectRow,
+  ColumnSelectWrapper,
+} from './styles';
+
+export { WHAT_IF_PANEL_WIDTH };
 
 interface WhatIfPanelProps {
   onClose: () => void;
@@ -250,50 +69,51 @@ interface WhatIfPanelProps {
 
 const WhatIfPanel = ({ onClose, topOffset }: WhatIfPanelProps) => {
   const theme = useTheme();
-  const dispatch = useDispatch();
 
-  const [selectedColumn, setSelectedColumn] = useState<string | null>(null);
+  // Local state for column selection and slider
+  const [selectedColumn, setSelectedColumn] = useState<string | undefined>(
+    undefined,
+  );
   const [sliderValue, setSliderValue] = useState<number>(SLIDER_DEFAULT);
-  const [affectedChartIds, setAffectedChartIds] = useState<number[]>([]);
   const [enableCascadingEffects, setEnableCascadingEffects] = useState(false);
-  const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
-  const [appliedModifications, setAppliedModifications] = useState<
-    ExtendedWhatIfModification[]
-  >([]);
-  // Counter that increments each time Apply is clicked, used as key to reset 
AI insights
-  const [applyCounter, setApplyCounter] = useState(0);
-  const [showAIReasoning, setShowAIReasoning] = useState(false);
-
-  // Filter state
-  const [filters, setFilters] = useState<WhatIfFilter[]>([]);
-  const [filterPopoverVisible, setFilterPopoverVisible] = useState(false);
-  const [editingFilterIndex, setEditingFilterIndex] = useState<number | null>(
-    null,
-  );
-  const [currentAdhocFilter, setCurrentAdhocFilter] =
-    useState<AdhocFilter | null>(null);
-
-  // AbortController for cancelling in-flight /suggest_related requests
-  const suggestionsAbortControllerRef = useRef<AbortController | null>(null);
 
-  // Cleanup: cancel any pending requests on unmount
-  useEffect(
-    () => () => {
-      suggestionsAbortControllerRef.current?.abort();
-    },
-    [],
-  );
+  // Custom hook for filter management
+  const {
+    filters,
+    filterPopoverVisible,
+    currentAdhocFilter,
+    setFilterPopoverVisible,
+    handleOpenFilterPopover,
+    handleEditFilter,
+    handleFilterChange,
+    handleRemoveFilter,
+    handleFilterPopoverClose,
+    handleFilterPopoverResize,
+    clearFilters,
+    formatFilterLabel,
+  } = useWhatIfFilters();
+
+  // Custom hook for apply logic and modifications
+  const {
+    appliedModifications,
+    affectedChartIds,
+    isLoadingSuggestions,
+    applyCounter,
+    handleApply,
+    handleDismissLoader,
+    aiInsightsModifications,
+  } = useWhatIfApply({
+    selectedColumn,
+    sliderValue,
+    filters,
+    enableCascadingEffects,
+  });
 
-  const slices = useSelector(
-    (state: RootState) => state.sliceEntities.slices as { [id: number]: Slice 
},
-  );
+  // Get numeric columns and datasources
+  const { numericColumns } = useNumericColumns();
   const datasources = useSelector((state: RootState) => state.datasources);
 
-  const numericColumns = useMemo(
-    () => getNumericColumnsForDashboard(slices, datasources),
-    [slices, datasources],
-  );
-
+  // Column options for the select dropdown
   const columnOptions = useMemo(
     () =>
       numericColumns.map(col => ({
@@ -303,21 +123,13 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
     [numericColumns],
   );
 
-  // Create a map from column name to affected chart IDs
-  const columnToChartIds = useMemo(() => {
-    const map = new Map<string, number[]>();
-    numericColumns.forEach((col: WhatIfColumn) => {
-      map.set(col.columnName, col.usedByChartIds);
-    });
-    return map;
-  }, [numericColumns]);
-
-  // Find the datasource for the selected column
+  // Find info about the selected column
   const selectedColumnInfo = useMemo(
     () => numericColumns.find(col => col.columnName === selectedColumn),
     [numericColumns, selectedColumn],
   );
 
+  // Find the datasource for the selected column
   const selectedDatasource = useMemo((): Datasource | null => {
     if (!selectedColumnInfo) return null;
     // Find datasource by ID - keys are in format "id__type"
@@ -334,298 +146,27 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
     return selectedDatasource.columns;
   }, [selectedDatasource]);
 
-  // Convert AdhocFilter to WhatIfFilter
-  const adhocFilterToWhatIfFilter = useCallback(
-    (adhocFilter: AdhocFilter): WhatIfFilter | null => {
-      if (!adhocFilter.isValid()) return null;
-
-      const { subject, operator, comparator } = adhocFilter;
-      if (!subject || !operator) return null;
-
-      // Map operator to WhatIfFilterOperator
-      let op = operator as WhatIfFilter['op'];
-
-      // Handle operator mapping
-      if (operator === 'TEMPORAL_RANGE') {
-        op = 'TEMPORAL_RANGE';
-      } else if (operator === 'IN' || operator === 'in') {
-        op = 'IN';
-      } else if (operator === 'NOT IN' || operator === 'not in') {
-        op = 'NOT IN';
-      }
-
-      return {
-        col: subject,
-        op,
-        val: comparator,
-      };
-    },
-    [],
-  );
-
-  // Convert WhatIfFilter to AdhocFilter for editing
-  const whatIfFilterToAdhocFilter = useCallback(
-    (filter: WhatIfFilter): AdhocFilter => {
-      // Find the operatorId from the operator
-      let operatorId: string | undefined;
-      for (const [key, value] of Object.entries(
-        OPERATOR_ENUM_TO_OPERATOR_TYPE,
-      )) {
-        if (value.operation === filter.op) {
-          operatorId = key;
-          break;
-        }
-      }
-
-      return new AdhocFilter({
-        expressionType: 'SIMPLE',
-        subject: filter.col,
-        operator: filter.op,
-        operatorId,
-        comparator: filter.val,
-        clause: Clauses.Where,
-      });
+  // Handle column selection change - also clears filters
+  const handleColumnChange = useCallback(
+    (value: string | undefined) => {
+      setSelectedColumn(value);
+      // Clear filters when column changes since they're tied to the datasource
+      clearFilters();
     },
-    [],
+    [clearFilters],
   );
 
-  const handleColumnChange = useCallback((value: string | null) => {
-    setSelectedColumn(value);
-    // Clear filters when column changes since they're tied to the datasource
-    setFilters([]);
-  }, []);
-
   const handleSliderChange = useCallback((value: number) => {
     setSliderValue(value);
   }, []);
 
-  // Filter handlers
-  const handleOpenFilterPopover = useCallback(() => {
-    // Create a new empty AdhocFilter
-    const newFilter = new AdhocFilter({
-      expressionType: 'SIMPLE',
-      clause: Clauses.Where,
-      subject: null,
-      operator: null,
-      comparator: null,
-      isNew: true,
-    });
-    setCurrentAdhocFilter(newFilter);
-    setEditingFilterIndex(null);
-    setFilterPopoverVisible(true);
+  const handleCascadingEffectsChange = useCallback((e: CheckboxChangeEvent) => 
{
+    setEnableCascadingEffects(e.target.checked);
   }, []);
 
-  const handleEditFilter = useCallback(
-    (index: number) => {
-      const filter = filters[index];
-      const adhocFilter = whatIfFilterToAdhocFilter(filter);
-      setCurrentAdhocFilter(adhocFilter);
-      setEditingFilterIndex(index);
-      setFilterPopoverVisible(true);
-    },
-    [filters, whatIfFilterToAdhocFilter],
-  );
-
-  const handleFilterChange = useCallback(
-    (adhocFilter: AdhocFilter) => {
-      const whatIfFilter = adhocFilterToWhatIfFilter(adhocFilter);
-      if (!whatIfFilter) return;
-
-      setFilters(prevFilters => {
-        if (editingFilterIndex !== null) {
-          // Update existing filter
-          const newFilters = [...prevFilters];
-          newFilters[editingFilterIndex] = whatIfFilter;
-          return newFilters;
-        }
-        // Add new filter
-        return [...prevFilters, whatIfFilter];
-      });
-      setFilterPopoverVisible(false);
-      setCurrentAdhocFilter(null);
-      setEditingFilterIndex(null);
-    },
-    [adhocFilterToWhatIfFilter, editingFilterIndex],
-  );
-
-  const handleRemoveFilter = useCallback(
-    (e: React.MouseEvent, index: number) => {
-      e.preventDefault();
-      e.stopPropagation();
-      setFilters(prevFilters => prevFilters.filter((_, i) => i !== index));
-    },
-    [],
-  );
-
-  const handleFilterPopoverClose = useCallback(() => {
-    setFilterPopoverVisible(false);
-    setCurrentAdhocFilter(null);
-    setEditingFilterIndex(null);
-  }, []);
-
-  // No-op handler for popover resize
-  const handleFilterPopoverResize = useCallback(() => {}, []);
-
-  const dashboardInfo = useSelector((state: RootState) => state.dashboardInfo);
-
-  const handleApply = useCallback(async () => {
-    if (!selectedColumn) return;
-
-    // Cancel any in-flight suggestions request
-    suggestionsAbortControllerRef.current?.abort();
-
-    // Immediately clear previous results and increment counter to reset AI 
insights component
-    setAppliedModifications([]);
-    setAffectedChartIds([]);
-    setApplyCounter(c => c + 1);
-
-    const multiplier = 1 + sliderValue / 100;
-
-    // Base user modification with filters
-    const userModification: ExtendedWhatIfModification = {
-      column: selectedColumn,
-      multiplier,
-      isAISuggested: false,
-      filters: filters.length > 0 ? filters : undefined,
-    };
-
-    let allModifications: ExtendedWhatIfModification[] = [userModification];
-
-    // If cascading effects enabled, fetch AI suggestions
-    if (enableCascadingEffects) {
-      // Create a new AbortController for this request
-      const abortController = new AbortController();
-      suggestionsAbortControllerRef.current = abortController;
-
-      setIsLoadingSuggestions(true);
-      try {
-        const suggestions = await fetchRelatedColumnSuggestions(
-          {
-            selectedColumn,
-            userMultiplier: multiplier,
-            availableColumns: numericColumns.map(col => ({
-              columnName: col.columnName,
-              description: col.description,
-              verboseName: col.verboseName,
-              datasourceId: col.datasourceId,
-            })),
-            dashboardName: dashboardInfo?.dash_edit_perm
-              ? dashboardInfo?.dashboard_title
-              : undefined,
-          },
-          abortController.signal,
-        );
-
-        // Add AI suggestions to modifications (with same filters as user 
modification)
-        const aiModifications: ExtendedWhatIfModification[] =
-          suggestions.suggestedModifications.map(mod => ({
-            column: mod.column,
-            multiplier: mod.multiplier,
-            isAISuggested: true,
-            reasoning: mod.reasoning,
-            confidence: mod.confidence,
-            filters: filters.length > 0 ? filters : undefined,
-          }));
-
-        allModifications = [...allModifications, ...aiModifications];
-      } catch (error) {
-        // Don't log or update state if the request was aborted
-        if (error instanceof Error && error.name === 'AbortError') {
-          return;
-        }
-        logging.error('Failed to get AI suggestions:', error);
-        // Continue with just user modification
-      }
-      setIsLoadingSuggestions(false);
-    }
-
-    setAppliedModifications(allModifications);
-
-    // Collect all affected chart IDs from all modifications
-    const allAffectedChartIds = new Set<number>();
-    allModifications.forEach(mod => {
-      const chartIds = columnToChartIds.get(mod.column) || [];
-      chartIds.forEach(id => allAffectedChartIds.add(id));
-    });
-    const chartIdsArray = Array.from(allAffectedChartIds);
-
-    // Save original chart data before applying what-if modifications
-    chartIdsArray.forEach(chartId => {
-      dispatch(saveOriginalChartData(chartId));
-    });
-
-    // Set the what-if modifications in Redux state (all modifications)
-    dispatch(
-      setWhatIfModifications(
-        allModifications.map(mod => ({
-          column: mod.column,
-          multiplier: mod.multiplier,
-          filters: mod.filters,
-        })),
-      ),
-    );
-
-    // Trigger queries for all affected charts
-    // This sets chart status to 'loading', which is important for AI insights 
timing
-    chartIdsArray.forEach(chartId => {
-      dispatch(triggerQuery(true, chartId));
-    });
-
-    // Set affected chart IDs AFTER Redux updates and query triggers
-    // This ensures WhatIfAIInsights mounts when charts are already loading,
-    // preventing it from immediately fetching with stale data
-    setAffectedChartIds(chartIdsArray);
-  }, [
-    dispatch,
-    selectedColumn,
-    sliderValue,
-    columnToChartIds,
-    enableCascadingEffects,
-    numericColumns,
-    dashboardInfo,
-    filters,
-  ]);
-
   const isApplyDisabled =
     !selectedColumn || sliderValue === SLIDER_DEFAULT || isLoadingSuggestions;
 
-  // Helper to format percentage change
-  const formatPercentage = (multiplier: number): string => {
-    const pct = (multiplier - 1) * 100;
-    const sign = pct >= 0 ? '+' : '';
-    return `${sign}${pct.toFixed(1)}%`;
-  };
-
-  // Helper to format filter for display (matching Explore filter label format)
-  const formatFilterLabel = (filter: WhatIfFilter): string => {
-    const { col, op, val } = filter;
-
-    // Special handling for TEMPORAL_RANGE to match Explore format
-    if (op === 'TEMPORAL_RANGE' && typeof val === 'string') {
-      return formatTimeRangeLabel(val, col);
-    }
-
-    let valStr: string;
-    if (Array.isArray(val)) {
-      valStr = val.join(', ');
-    } else if (typeof val === 'boolean') {
-      valStr = val ? 'true' : 'false';
-    } else {
-      valStr = String(val);
-    }
-    // Truncate long values
-    if (valStr.length > 20) {
-      valStr = `${valStr.substring(0, 17)}...`;
-    }
-    return `${col} ${op} ${valStr}`;
-  };
-
-  const sliderMarks = {
-    [SLIDER_MIN]: `${SLIDER_MIN}%`,
-    0: '0%',
-    [SLIDER_MAX]: `+${SLIDER_MAX}%`,
-  };
-
   return (
     <PanelContainer data-test="what-if-panel" topOffset={topOffset}>
       <PanelHeader>
@@ -642,6 +183,7 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
           <Icons.CloseOutlined iconSize="m" />
         </CloseButton>
       </PanelHeader>
+
       <PanelContent>
         <FormSection>
           <Label>{t('Select column to adjust')}</Label>
@@ -657,77 +199,23 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
                 ariaLabel={t('Select column to adjust')}
               />
             </ColumnSelectWrapper>
-            <Popover
-              open={filterPopoverVisible}
-              onOpenChange={setFilterPopoverVisible}
-              trigger="click"
-              placement="left"
-              destroyOnHidden
-              content={
-                currentAdhocFilter && selectedDatasource ? (
-                  <FilterPopoverContent>
-                    <AdhocFilterEditPopover
-                      adhocFilter={currentAdhocFilter}
-                      options={filterColumnOptions}
-                      datasource={selectedDatasource}
-                      onChange={handleFilterChange}
-                      onClose={handleFilterPopoverClose}
-                      onResize={handleFilterPopoverResize}
-                      requireSave
-                    />
-                  </FilterPopoverContent>
-                ) : null
-              }
-            >
-              <Tooltip
-                title={
-                  selectedColumn
-                    ? t('Add filter to scope the modification')
-                    : t('Select a column first')
-                }
-              >
-                <FilterButton
-                  onClick={handleOpenFilterPopover}
-                  disabled={!selectedColumn || !selectedDatasource}
-                  aria-label={t('Add filter')}
-                  buttonStyle="tertiary"
-                >
-                  <Icons.FilterOutlined iconSize="m" />
-                </FilterButton>
-              </Tooltip>
-            </Popover>
+            <FilterSection
+              filters={filters}
+              filterPopoverVisible={filterPopoverVisible}
+              currentAdhocFilter={currentAdhocFilter}
+              selectedColumn={selectedColumn}
+              selectedDatasource={selectedDatasource}
+              filterColumnOptions={filterColumnOptions}
+              onOpenFilterPopover={handleOpenFilterPopover}
+              onFilterPopoverVisibleChange={setFilterPopoverVisible}
+              onFilterChange={handleFilterChange}
+              onFilterPopoverClose={handleFilterPopoverClose}
+              onFilterPopoverResize={handleFilterPopoverResize}
+              onEditFilter={handleEditFilter}
+              onRemoveFilter={handleRemoveFilter}
+              formatFilterLabel={formatFilterLabel}
+            />
           </ColumnSelectRow>
-          {filters.length > 0 && (
-            <FiltersSection>
-              <Label
-                css={css`
-                  font-size: ${theme.fontSizeSM}px;
-                  color: ${theme.colorTextSecondary};
-                `}
-              >
-                {t('Filters')}
-              </Label>
-              <FilterTagsContainer>
-                {filters.map((filter, index) => (
-                  <Tag
-                    key={`${filter.col}-${filter.op}-${index}`}
-                    closable
-                    onClose={e => handleRemoveFilter(e, index)}
-                    onClick={() => handleEditFilter(index)}
-                    css={css`
-                      cursor: pointer;
-                      margin: 0;
-                      &:hover {
-                        opacity: 0.8;
-                      }
-                    `}
-                  >
-                    {formatFilterLabel(filter)}
-                  </Tag>
-                ))}
-              </FilterTagsContainer>
-            </FiltersSection>
-          )}
         </FormSection>
 
         <FormSection>
@@ -738,11 +226,8 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
               max={SLIDER_MAX}
               value={sliderValue}
               onChange={handleSliderChange}
-              marks={sliderMarks}
-              tooltip={{
-                formatter: (value?: number) =>
-                  value !== undefined ? `${value > 0 ? '+' : ''}${value}%` : 
'',
-              }}
+              marks={SLIDER_MARKS}
+              tooltip={SLIDER_TOOLTIP_CONFIG}
             />
           </SliderContainer>
         </FormSection>
@@ -750,13 +235,13 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
         <CheckboxContainer>
           <Checkbox
             checked={enableCascadingEffects}
-            onChange={e => setEnableCascadingEffects(e.target.checked)}
+            onChange={handleCascadingEffectsChange}
           >
-            {t('AI-powered cascading effects')}
+            {t('Show the bigger picture with AI')}
           </Checkbox>
           <Tooltip
             title={t(
-              'When enabled, AI will analyze column relationships and 
automatically suggest related columns that should also be modified.',
+              'Automatically includes related metrics and columns affected by 
this change. AI infers relationships based on how metrics and columns are used 
across the dashboard.',
             )}
           >
             <Icons.InfoCircleOutlined
@@ -791,81 +276,19 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
           />
         )}
 
-        {appliedModifications.length > 0 && (
-          <ModificationsSection>
-            <ModificationTagsContainer>
-              {appliedModifications.map((mod, idx) => (
-                <Tag
-                  key={idx}
-                  css={css`
-                    display: inline-flex;
-                    align-items: center;
-                    gap: ${theme.sizeUnit}px;
-                    margin: 0;
-                  `}
-                >
-                  <span>{mod.column}</span>
-                  {mod.isAISuggested && <AIBadge>{t('AI')}</AIBadge>}
-                  <span
-                    css={css`
-                      font-weight: ${theme.fontWeightStrong};
-                      color: ${mod.multiplier >= 1
-                        ? theme.colorSuccess
-                        : theme.colorError};
-                    `}
-                  >
-                    {formatPercentage(mod.multiplier)}
-                  </span>
-                </Tag>
-              ))}
-            </ModificationTagsContainer>
-            {appliedModifications.some(mod => mod.reasoning) && (
-              <AIReasoningSection>
-                <AIReasoningToggle
-                  onClick={() => setShowAIReasoning(!showAIReasoning)}
-                >
-                  {showAIReasoning ? (
-                    <Icons.DownOutlined iconSize="xs" />
-                  ) : (
-                    <Icons.RightOutlined iconSize="xs" />
-                  )}
-                  {t('How AI chose these')}
-                </AIReasoningToggle>
-                {showAIReasoning && (
-                  <AIReasoningContent>
-                    {appliedModifications
-                      .filter(mod => mod.reasoning)
-                      .map((mod, idx) => (
-                        <AIReasoningItem key={idx}>
-                          <strong>{mod.column}:</strong> {mod.reasoning}
-                        </AIReasoningItem>
-                      ))}
-                  </AIReasoningContent>
-                )}
-              </AIReasoningSection>
-            )}
-          </ModificationsSection>
-        )}
+        <ModificationsDisplay modifications={appliedModifications} />
 
         {affectedChartIds.length > 0 && (
           <WhatIfAIInsights
             key={applyCounter}
             affectedChartIds={affectedChartIds}
-            modifications={appliedModifications.map(mod => ({
-              column: mod.column,
-              multiplier: mod.multiplier,
-              filters: mod.filters,
-            }))}
+            modifications={aiInsightsModifications}
           />
         )}
       </PanelContent>
+
       {isLoadingSuggestions && (
-        <MagicWandLoader
-          onDismiss={() => {
-            suggestionsAbortControllerRef.current?.abort();
-            setIsLoadingSuggestions(false);
-          }}
-        />
+        <HarryPotterWandLoader onDismiss={handleDismissLoader} />
       )}
     </PanelContainer>
   );
diff --git a/superset-frontend/src/dashboard/components/WhatIfDrawer/styles.ts 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/styles.ts
new file mode 100644
index 0000000000..34154b061d
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/WhatIfDrawer/styles.ts
@@ -0,0 +1,207 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { styled } from '@apache-superset/core/ui';
+import { Button } from '@superset-ui/core/components';
+import { WHAT_IF_PANEL_WIDTH } from './constants';
+
+export const PanelContainer = styled.div<{ topOffset: number }>`
+  grid-column: 2;
+  grid-row: 1 / -1; /* Span all rows */
+  width: ${WHAT_IF_PANEL_WIDTH}px;
+  min-width: ${WHAT_IF_PANEL_WIDTH}px;
+  background-color: ${({ theme }) => theme.colorBgContainer};
+  border-left: 1px solid ${({ theme }) => theme.colorBorderSecondary};
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  position: sticky;
+  top: ${({ topOffset }) => topOffset}px;
+  height: calc(100vh - ${({ topOffset }) => topOffset}px);
+  align-self: start;
+  z-index: 10;
+`;
+
+export const PanelHeader = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: ${({ theme }) => theme.sizeUnit * 3}px
+    ${({ theme }) => theme.sizeUnit * 4}px;
+  border-bottom: 1px solid ${({ theme }) => theme.colorBorderSecondary};
+`;
+
+export const PanelTitle = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+  font-weight: ${({ theme }) => theme.fontWeightStrong};
+  font-size: ${({ theme }) => theme.fontSizeLG}px;
+`;
+
+export const CloseButton = styled.button`
+  background: none;
+  border: none;
+  cursor: pointer;
+  padding: ${({ theme }) => theme.sizeUnit}px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: ${({ theme }) => theme.colorTextSecondary};
+  border-radius: ${({ theme }) => theme.borderRadius}px;
+
+  &:hover {
+    background-color: ${({ theme }) => theme.colorBgTextHover};
+    color: ${({ theme }) => theme.colorText};
+  }
+`;
+
+export const PanelContent = styled.div`
+  flex: 1;
+  overflow-y: auto;
+  padding: ${({ theme }) => theme.sizeUnit * 4}px;
+  display: flex;
+  flex-direction: column;
+  gap: ${({ theme }) => theme.sizeUnit * 5}px;
+`;
+
+export const FormSection = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+export const Label = styled.label`
+  color: ${({ theme }) => theme.colorText};
+`;
+
+export const SliderContainer = styled.div`
+  padding: 0 ${({ theme }) => theme.sizeUnit}px;
+  & .ant-slider-mark {
+    font-size: ${({ theme }) => theme.fontSizeSM}px;
+  }
+`;
+
+export const ApplyButton = styled(Button)`
+  width: 100%;
+  min-height: 32px;
+`;
+
+export const CheckboxContainer = styled.div`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit}px;
+`;
+
+export const ModificationsSection = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: ${({ theme }) => theme.sizeUnit * 5}px;
+`;
+
+export const ModificationTagsContainer = styled.div`
+  display: flex;
+  flex-wrap: wrap;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+export const AIBadge = styled.span`
+  font-size: 10px;
+  padding: 0 4px;
+  background-color: ${({ theme }) => theme.colorInfo};
+  color: ${({ theme }) => theme.colorWhite};
+  border-radius: 16px;
+  line-height: 1.2;
+`;
+
+export const AIReasoningSection = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: ${({ theme }) => theme.sizeUnit}px;
+`;
+
+export const AIReasoningToggle = styled.button`
+  display: flex;
+  align-items: center;
+  gap: ${({ theme }) => theme.sizeUnit}px;
+  background: none;
+  border: none;
+  padding: 0;
+  cursor: pointer;
+  color: ${({ theme }) => theme.colorTextTertiary};
+  font-size: ${({ theme }) => theme.fontSizeSM}px;
+
+  &:hover {
+    color: ${({ theme }) => theme.colorText};
+  }
+`;
+
+export const AIReasoningContent = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: ${({ theme }) => theme.sizeUnit}px;
+  padding-left: ${({ theme }) => theme.sizeUnit * 4}px;
+`;
+
+export const AIReasoningItem = styled.div`
+  font-size: ${({ theme }) => theme.fontSizeSM}px;
+  color: ${({ theme }) => theme.colorTextSecondary};
+`;
+
+export const ColumnSelectRow = styled.div`
+  display: flex;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+  align-items: flex-start;
+`;
+
+export const ColumnSelectWrapper = styled.div`
+  flex: 1;
+  min-width: 0;
+`;
+
+export const FilterButton = styled(Button)`
+  flex-shrink: 0;
+  padding: 0 ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+export const FilterPopoverContent = styled.div`
+  .edit-popover-resize {
+    transform: scaleX(-1);
+    float: right;
+    margin-top: ${({ theme }) => theme.sizeUnit * 4}px;
+    margin-right: ${({ theme }) => theme.sizeUnit * -1}px;
+    color: ${({ theme }) => theme.colorIcon};
+    cursor: nwse-resize;
+  }
+  .filter-sql-editor {
+    border: ${({ theme }) => theme.colorBorder} solid thin;
+  }
+`;
+
+export const FiltersSection = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: ${({ theme }) => theme.sizeUnit * 2}px;
+`;
+
+export const FilterTagsContainer = styled.div`
+  display: flex;
+  flex-wrap: wrap;
+  gap: ${({ theme }) => theme.sizeUnit}px;
+`;
diff --git a/superset-frontend/src/dashboard/components/WhatIfDrawer/types.ts 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/types.ts
index 2c1ea2a02d..838ad6770c 100644
--- a/superset-frontend/src/dashboard/components/WhatIfDrawer/types.ts
+++ b/superset-frontend/src/dashboard/components/WhatIfDrawer/types.ts
@@ -17,6 +17,17 @@
  * under the License.
  */
 
+// Import shared types for internal use
+import type {
+  WhatIfFilter,
+  WhatIfFilterOperator,
+  WhatIfModification,
+} from 'src/dashboard/types';
+
+// Re-export shared types from dashboard/types.ts
+export type { WhatIfFilter, WhatIfFilterOperator, WhatIfModification };
+
+// Types specific to chart comparison display
 export interface ChartMetricComparison {
   metricName: string;
   originalValue: number;
@@ -31,23 +42,7 @@ export interface ChartComparison {
   metrics: ChartMetricComparison[];
 }
 
-export type WhatIfFilterOperator =
-  | '=='
-  | '!='
-  | '>'
-  | '<'
-  | '>='
-  | '<='
-  | 'IN'
-  | 'NOT IN'
-  | 'TEMPORAL_RANGE';
-
-export interface WhatIfFilter {
-  col: string;
-  op: WhatIfFilterOperator;
-  val: string | number | boolean | Array<string | number>;
-}
-
+// Types for /interpret API endpoint
 export interface WhatIfInterpretRequest {
   modifications: Array<{
     column: string;
@@ -72,8 +67,7 @@ export interface WhatIfInterpretResponse {
 
 export type WhatIfAIStatus = 'idle' | 'loading' | 'success' | 'error';
 
-// Types for suggest_related endpoint
-
+// Types for /suggest_related API endpoint
 export interface AvailableColumn {
   columnName: string;
   description?: string | null;
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/useChartComparison.ts 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/useChartComparison.ts
index b5d75dd949..54aea40364 100644
--- 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/useChartComparison.ts
+++ 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/useChartComparison.ts
@@ -128,11 +128,10 @@ export function useIsChartInActiveTab() {
 export function useChartsInActiveTabs(chartIds: number[]): number[] {
   const isChartInActiveTab = useIsChartInActiveTab();
 
-  return useMemo(() => {
-    const visibleCharts = chartIds.filter(isChartInActiveTab);
-    console.log('[useChartsInActiveTabs] Visible charts:', visibleCharts);
-    return visibleCharts;
-  }, [chartIds, isChartInActiveTab]);
+  return useMemo(
+    () => chartIds.filter(isChartInActiveTab),
+    [chartIds, isChartInActiveTab],
+  );
 }
 
 interface ChartComparisonData {
@@ -222,11 +221,6 @@ export function useChartComparison(
   return useMemo(() => {
     const comparisons: ChartComparison[] = [];
 
-    console.log(
-      '[useChartComparison] Processing visible charts:',
-      visibleChartIds,
-    );
-
     for (const chartId of visibleChartIds) {
       const chartState = chartData[chartId];
       const displayData = chartDisplayData[chartId];
@@ -240,9 +234,6 @@ export function useChartComparison(
       // Skip if original and modified data are the same reference
       // This indicates the what-if query hasn't completed yet (race condition 
guard)
       if (originalData === modifiedData) {
-        console.warn(
-          `[useChartComparison] Chart ${chartId}: originalData === 
modifiedData (same reference), skipping`,
-        );
         continue;
       }
 
@@ -251,7 +242,7 @@ export function useChartComparison(
       const coltypes = chartState.coltypes || [];
       const metrics: ChartMetricComparison[] = [];
 
-      for (let i = 0; i < colnames.length; i++) {
+      for (let i = 0; i < colnames.length; i += 1) {
         const metricName = colnames[i];
         const coltype = coltypes[i];
 
@@ -329,12 +320,6 @@ export function useAllChartsLoaded(chartIds: number[]): 
boolean {
   const chartStatuses = useChartLoadingStatuses(visibleChartIds);
 
   return useMemo(() => {
-    const statuses = visibleChartIds.map(id => ({
-      id,
-      status: chartStatuses[id],
-    }));
-    console.log('[useAllChartsLoaded] Chart statuses:', statuses);
-
     // Require explicit completion status, not just "not loading"
     // This prevents race conditions during state transitions
     // Include 'failed' to avoid waiting indefinitely for charts that errored
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/useWhatIfApply.ts 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/useWhatIfApply.ts
new file mode 100644
index 0000000000..7587b6dc14
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/WhatIfDrawer/useWhatIfApply.ts
@@ -0,0 +1,233 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { logging } from '@superset-ui/core';
+import { setWhatIfModifications } from 'src/dashboard/actions/dashboardState';
+import {
+  triggerQuery,
+  saveOriginalChartData,
+} from 'src/components/Chart/chartAction';
+import { RootState, WhatIfFilter } from 'src/dashboard/types';
+import { useNumericColumns } from 'src/dashboard/util/useNumericColumns';
+import { fetchRelatedColumnSuggestions } from './whatIfApi';
+import { ExtendedWhatIfModification, WhatIfModification } from './types';
+
+export interface UseWhatIfApplyParams {
+  selectedColumn: string | undefined;
+  sliderValue: number;
+  filters: WhatIfFilter[];
+  enableCascadingEffects: boolean;
+}
+
+export interface UseWhatIfApplyReturn {
+  appliedModifications: ExtendedWhatIfModification[];
+  affectedChartIds: number[];
+  isLoadingSuggestions: boolean;
+  applyCounter: number;
+  handleApply: () => Promise<void>;
+  handleDismissLoader: () => void;
+  aiInsightsModifications: WhatIfModification[];
+}
+
+/**
+ * Custom hook for managing what-if apply logic and modifications state.
+ * Handles:
+ * - Applied modifications tracking
+ * - AI suggestions fetching with cascading effects
+ * - Redux dispatching for what-if state
+ * - Chart query triggering
+ */
+export function useWhatIfApply({
+  selectedColumn,
+  sliderValue,
+  filters,
+  enableCascadingEffects,
+}: UseWhatIfApplyParams): UseWhatIfApplyReturn {
+  const dispatch = useDispatch();
+
+  const [appliedModifications, setAppliedModifications] = useState<
+    ExtendedWhatIfModification[]
+  >([]);
+  const [affectedChartIds, setAffectedChartIds] = useState<number[]>([]);
+  const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
+  // Counter that increments each time Apply is clicked, used as key to reset 
AI insights
+  const [applyCounter, setApplyCounter] = useState(0);
+
+  // AbortController for cancelling in-flight /suggest_related requests
+  const suggestionsAbortControllerRef = useRef<AbortController | null>(null);
+
+  const { numericColumns, columnToChartIds } = useNumericColumns();
+  const dashboardInfo = useSelector((state: RootState) => state.dashboardInfo);
+
+  // Cleanup: cancel any pending requests on unmount
+  useEffect(
+    () => () => {
+      suggestionsAbortControllerRef.current?.abort();
+    },
+    [],
+  );
+
+  const handleApply = useCallback(async () => {
+    if (!selectedColumn) return;
+
+    // Cancel any in-flight suggestions request
+    suggestionsAbortControllerRef.current?.abort();
+
+    // Immediately clear previous results and increment counter to reset AI 
insights component
+    setAppliedModifications([]);
+    setAffectedChartIds([]);
+    setApplyCounter(c => c + 1);
+
+    const multiplier = 1 + sliderValue / 100;
+
+    // Base user modification with filters
+    const userModification: ExtendedWhatIfModification = {
+      column: selectedColumn,
+      multiplier,
+      isAISuggested: false,
+      filters: filters.length > 0 ? filters : undefined,
+    };
+
+    let allModifications: ExtendedWhatIfModification[] = [userModification];
+
+    // If cascading effects enabled, fetch AI suggestions
+    if (enableCascadingEffects) {
+      // Create a new AbortController for this request
+      const abortController = new AbortController();
+      suggestionsAbortControllerRef.current = abortController;
+
+      setIsLoadingSuggestions(true);
+      try {
+        const suggestions = await fetchRelatedColumnSuggestions(
+          {
+            selectedColumn,
+            userMultiplier: multiplier,
+            availableColumns: numericColumns.map(col => ({
+              columnName: col.columnName,
+              description: col.description,
+              verboseName: col.verboseName,
+              datasourceId: col.datasourceId,
+            })),
+            dashboardName: dashboardInfo?.dash_edit_perm
+              ? dashboardInfo?.dashboard_title
+              : undefined,
+          },
+          abortController.signal,
+        );
+
+        // Add AI suggestions to modifications (with same filters as user 
modification)
+        const aiModifications: ExtendedWhatIfModification[] =
+          suggestions.suggestedModifications.map(mod => ({
+            column: mod.column,
+            multiplier: mod.multiplier,
+            isAISuggested: true,
+            reasoning: mod.reasoning,
+            confidence: mod.confidence,
+            filters: filters.length > 0 ? filters : undefined,
+          }));
+
+        allModifications = [...allModifications, ...aiModifications];
+      } catch (error) {
+        // Don't log or update state if the request was aborted
+        if (error instanceof Error && error.name === 'AbortError') {
+          return;
+        }
+        logging.error('Failed to get AI suggestions:', error);
+        // Continue with just user modification
+      }
+      setIsLoadingSuggestions(false);
+    }
+
+    setAppliedModifications(allModifications);
+
+    // Collect all affected chart IDs from all modifications
+    const allAffectedChartIds = new Set<number>();
+    allModifications.forEach(mod => {
+      const chartIds = columnToChartIds.get(mod.column) || [];
+      chartIds.forEach(id => allAffectedChartIds.add(id));
+    });
+    const chartIdsArray = Array.from(allAffectedChartIds);
+
+    // Save original chart data before applying what-if modifications
+    chartIdsArray.forEach(chartId => {
+      dispatch(saveOriginalChartData(chartId));
+    });
+
+    // Set the what-if modifications in Redux state (all modifications)
+    dispatch(
+      setWhatIfModifications(
+        allModifications.map(mod => ({
+          column: mod.column,
+          multiplier: mod.multiplier,
+          filters: mod.filters,
+        })),
+      ),
+    );
+
+    // Trigger queries for all affected charts
+    // This sets chart status to 'loading', which is important for AI insights 
timing
+    chartIdsArray.forEach(chartId => {
+      dispatch(triggerQuery(true, chartId));
+    });
+
+    // Set affected chart IDs AFTER Redux updates and query triggers
+    // This ensures WhatIfAIInsights mounts when charts are already loading,
+    // preventing it from immediately fetching with stale data
+    setAffectedChartIds(chartIdsArray);
+  }, [
+    dispatch,
+    selectedColumn,
+    sliderValue,
+    columnToChartIds,
+    enableCascadingEffects,
+    numericColumns,
+    dashboardInfo,
+    filters,
+  ]);
+
+  const handleDismissLoader = useCallback(() => {
+    suggestionsAbortControllerRef.current?.abort();
+    setIsLoadingSuggestions(false);
+  }, []);
+
+  // Memoize modifications array for WhatIfAIInsights to prevent unnecessary 
re-renders
+  const aiInsightsModifications = useMemo(
+    () =>
+      appliedModifications.map(mod => ({
+        column: mod.column,
+        multiplier: mod.multiplier,
+        filters: mod.filters,
+      })),
+    [appliedModifications],
+  );
+
+  return {
+    appliedModifications,
+    affectedChartIds,
+    isLoadingSuggestions,
+    applyCounter,
+    handleApply,
+    handleDismissLoader,
+    aiInsightsModifications,
+  };
+}
+
+export default useWhatIfApply;
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/useWhatIfFilters.ts 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/useWhatIfFilters.ts
new file mode 100644
index 0000000000..173f4c98ae
--- /dev/null
+++ 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/useWhatIfFilters.ts
@@ -0,0 +1,227 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useCallback, useState } from 'react';
+import { formatTimeRangeLabel } from '@superset-ui/core';
+import { WhatIfFilter } from 'src/dashboard/types';
+import AdhocFilter from 
'src/explore/components/controls/FilterControl/AdhocFilter';
+import { Clauses } from 'src/explore/components/controls/FilterControl/types';
+import { OPERATOR_ENUM_TO_OPERATOR_TYPE } from 'src/explore/constants';
+
+export interface UseWhatIfFiltersReturn {
+  filters: WhatIfFilter[];
+  filterPopoverVisible: boolean;
+  editingFilterIndex: number | null;
+  currentAdhocFilter: AdhocFilter | null;
+  setFilterPopoverVisible: (visible: boolean) => void;
+  handleOpenFilterPopover: () => void;
+  handleEditFilter: (index: number) => void;
+  handleFilterChange: (adhocFilter: AdhocFilter) => void;
+  handleRemoveFilter: (e: React.MouseEvent, index: number) => void;
+  handleFilterPopoverClose: () => void;
+  handleFilterPopoverResize: () => void;
+  clearFilters: () => void;
+  formatFilterLabel: (filter: WhatIfFilter) => string;
+}
+
+/**
+ * Custom hook for managing what-if filter state and operations.
+ * Encapsulates all filter-related logic including:
+ * - Filter CRUD operations
+ * - AdhocFilter <-> WhatIfFilter conversions
+ * - Popover state management
+ * - Filter label formatting
+ */
+export function useWhatIfFilters(): UseWhatIfFiltersReturn {
+  const [filters, setFilters] = useState<WhatIfFilter[]>([]);
+  const [filterPopoverVisible, setFilterPopoverVisible] = useState(false);
+  const [editingFilterIndex, setEditingFilterIndex] = useState<number | null>(
+    null,
+  );
+  const [currentAdhocFilter, setCurrentAdhocFilter] =
+    useState<AdhocFilter | null>(null);
+
+  // Convert AdhocFilter to WhatIfFilter
+  const adhocFilterToWhatIfFilter = useCallback(
+    (adhocFilter: AdhocFilter): WhatIfFilter | null => {
+      if (!adhocFilter.isValid()) return null;
+
+      const { subject, operator, comparator } = adhocFilter;
+      if (!subject || !operator) return null;
+
+      // Map operator to WhatIfFilterOperator
+      let op = operator as WhatIfFilter['op'];
+
+      // Handle operator mapping
+      if (operator === 'TEMPORAL_RANGE') {
+        op = 'TEMPORAL_RANGE';
+      } else if (operator === 'IN' || operator === 'in') {
+        op = 'IN';
+      } else if (operator === 'NOT IN' || operator === 'not in') {
+        op = 'NOT IN';
+      }
+
+      return {
+        col: subject,
+        op,
+        val: comparator,
+      };
+    },
+    [],
+  );
+
+  // Convert WhatIfFilter to AdhocFilter for editing
+  const whatIfFilterToAdhocFilter = useCallback(
+    (filter: WhatIfFilter): AdhocFilter => {
+      // Find the operatorId from the operator
+      let operatorId: string | undefined;
+      for (const [key, value] of Object.entries(
+        OPERATOR_ENUM_TO_OPERATOR_TYPE,
+      )) {
+        if (value.operation === filter.op) {
+          operatorId = key;
+          break;
+        }
+      }
+
+      return new AdhocFilter({
+        expressionType: 'SIMPLE',
+        subject: filter.col,
+        operator: filter.op,
+        operatorId,
+        comparator: filter.val,
+        clause: Clauses.Where,
+      });
+    },
+    [],
+  );
+
+  const handleOpenFilterPopover = useCallback(() => {
+    // Create a new empty AdhocFilter
+    const newFilter = new AdhocFilter({
+      expressionType: 'SIMPLE',
+      clause: Clauses.Where,
+      subject: null,
+      operator: null,
+      comparator: null,
+      isNew: true,
+    });
+    setCurrentAdhocFilter(newFilter);
+    setEditingFilterIndex(null);
+    setFilterPopoverVisible(true);
+  }, []);
+
+  const handleEditFilter = useCallback(
+    (index: number) => {
+      const filter = filters[index];
+      const adhocFilter = whatIfFilterToAdhocFilter(filter);
+      setCurrentAdhocFilter(adhocFilter);
+      setEditingFilterIndex(index);
+      setFilterPopoverVisible(true);
+    },
+    [filters, whatIfFilterToAdhocFilter],
+  );
+
+  const handleFilterChange = useCallback(
+    (adhocFilter: AdhocFilter) => {
+      const whatIfFilter = adhocFilterToWhatIfFilter(adhocFilter);
+      if (!whatIfFilter) return;
+
+      setFilters(prevFilters => {
+        if (editingFilterIndex !== null) {
+          // Update existing filter
+          const newFilters = [...prevFilters];
+          newFilters[editingFilterIndex] = whatIfFilter;
+          return newFilters;
+        }
+        // Add new filter
+        return [...prevFilters, whatIfFilter];
+      });
+      setFilterPopoverVisible(false);
+      setCurrentAdhocFilter(null);
+      setEditingFilterIndex(null);
+    },
+    [adhocFilterToWhatIfFilter, editingFilterIndex],
+  );
+
+  const handleRemoveFilter = useCallback(
+    (e: React.MouseEvent, index: number) => {
+      e.preventDefault();
+      e.stopPropagation();
+      setFilters(prevFilters => prevFilters.filter((_, i) => i !== index));
+    },
+    [],
+  );
+
+  const handleFilterPopoverClose = useCallback(() => {
+    setFilterPopoverVisible(false);
+    setCurrentAdhocFilter(null);
+    setEditingFilterIndex(null);
+  }, []);
+
+  // Intentionally empty: AdhocFilterEditPopover requires an onResize callback,
+  // but we don't need dynamic resizing in this fixed-width panel context.
+  const handleFilterPopoverResize = useCallback(() => {}, []);
+
+  const clearFilters = useCallback(() => {
+    setFilters([]);
+  }, []);
+
+  // Helper to format filter for display (matching Explore filter label format)
+  const formatFilterLabel = useCallback((filter: WhatIfFilter): string => {
+    const { col, op, val } = filter;
+
+    // Special handling for TEMPORAL_RANGE to match Explore format
+    if (op === 'TEMPORAL_RANGE' && typeof val === 'string') {
+      return formatTimeRangeLabel(val, col);
+    }
+
+    let valStr: string;
+    if (Array.isArray(val)) {
+      valStr = val.join(', ');
+    } else if (typeof val === 'boolean') {
+      valStr = val ? 'true' : 'false';
+    } else {
+      valStr = String(val);
+    }
+    // Truncate long values
+    if (valStr.length > 20) {
+      valStr = `${valStr.substring(0, 17)}...`;
+    }
+    return `${col} ${op} ${valStr}`;
+  }, []);
+
+  return {
+    filters,
+    filterPopoverVisible,
+    editingFilterIndex,
+    currentAdhocFilter,
+    setFilterPopoverVisible,
+    handleOpenFilterPopover,
+    handleEditFilter,
+    handleFilterChange,
+    handleRemoveFilter,
+    handleFilterPopoverClose,
+    handleFilterPopoverResize,
+    clearFilters,
+    formatFilterLabel,
+  };
+}
+
+export default useWhatIfFilters;
diff --git a/superset-frontend/src/dashboard/types.ts 
b/superset-frontend/src/dashboard/types.ts
index 48cce50f45..73b6497dcb 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -131,6 +131,7 @@ export type DashboardState = {
   };
   chartStates?: Record<string, any>;
   whatIfModifications: WhatIfModification[];
+  whatIfPanelOpen?: boolean;
 };
 export type DashboardInfo = {
   id: number;
diff --git a/superset-frontend/src/dashboard/util/useNumericColumns.ts 
b/superset-frontend/src/dashboard/util/useNumericColumns.ts
new file mode 100644
index 0000000000..7a4b839ac0
--- /dev/null
+++ b/superset-frontend/src/dashboard/util/useNumericColumns.ts
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useMemo } from 'react';
+import { useSelector } from 'react-redux';
+import { RootState, Slice, WhatIfColumn } from 'src/dashboard/types';
+import { getNumericColumnsForDashboard } from './whatIf';
+
+/**
+ * Hook to get numeric columns available for what-if analysis on the dashboard.
+ * This hook memoizes the computation and provides a stable reference to avoid
+ * unnecessary re-renders in consuming components.
+ *
+ * Returns:
+ * - numericColumns: Array of WhatIfColumn objects with column metadata
+ * - columnToChartIds: Map from column name to array of chart IDs that use it
+ */
+export function useNumericColumns(): {
+  numericColumns: WhatIfColumn[];
+  columnToChartIds: Map<string, number[]>;
+} {
+  const slices = useSelector(
+    (state: RootState) => state.sliceEntities.slices as { [id: number]: Slice 
},
+  );
+  const datasources = useSelector((state: RootState) => state.datasources);
+
+  const numericColumns = useMemo(
+    () => getNumericColumnsForDashboard(slices, datasources),
+    [slices, datasources],
+  );
+
+  const columnToChartIds = useMemo(() => {
+    const map = new Map<string, number[]>();
+    numericColumns.forEach(col => {
+      map.set(col.columnName, col.usedByChartIds);
+    });
+    return map;
+  }, [numericColumns]);
+
+  return { numericColumns, columnToChartIds };
+}
+
+export default useNumericColumns;
diff --git a/superset-frontend/src/dashboard/util/whatIf.ts 
b/superset-frontend/src/dashboard/util/whatIf.ts
index caed76f04b..9d7d6f11b6 100644
--- a/superset-frontend/src/dashboard/util/whatIf.ts
+++ b/superset-frontend/src/dashboard/util/whatIf.ts
@@ -16,11 +16,40 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { ensureIsArray, getColumnLabel } from '@superset-ui/core';
+import {
+  ensureIsArray,
+  getColumnLabel,
+  isQueryFormColumn,
+  JsonValue,
+} from '@superset-ui/core';
 import { GenericDataType } from '@apache-superset/core/api/core';
 import { ColumnMeta } from '@superset-ui/chart-controls';
 import { DatasourcesState, Slice, WhatIfColumn } from '../types';
 
+/**
+ * Type definitions for form_data structures used in what-if analysis.
+ * These are local types for the subset of form_data we need to inspect.
+ */
+
+/** Metric definition in form_data */
+interface FormDataMetric {
+  expressionType?: 'SIMPLE' | 'SQL';
+  column?: string | { column_name: string };
+  aggregate?: string;
+  sqlExpression?: string;
+  label?: string;
+}
+
+/** Filter definition in form_data */
+interface FormDataFilter {
+  expressionType?: 'SIMPLE' | 'SQL';
+  subject?: string;
+  operator?: string;
+  comparator?: JsonValue;
+  sqlExpression?: string;
+  clause?: string;
+}
+
 /**
  * Check if a column is numeric based on its type_generic field
  */
@@ -156,8 +185,8 @@ export function extractColumnsFromSlice(slice: Slice): 
Set<string> {
   if (!formData) return columns;
 
   // Helper to add column - handles both physical columns (strings) and adhoc 
columns
-  const addColumn = (col: any) => {
-    if (col) {
+  const addColumn = (col: unknown) => {
+    if (isQueryFormColumn(col)) {
       const label = getColumnLabel(col);
       if (label) columns.add(label);
     }
@@ -172,7 +201,7 @@ export function extractColumnsFromSlice(slice: Slice): 
Set<string> {
   }
 
   // Extract metrics - get column names from metric definitions
-  ensureIsArray(formData.metrics).forEach((metric: any) => {
+  ensureIsArray(formData.metrics).forEach((metric: string | FormDataMetric) => 
{
     if (typeof metric === 'string') {
       // Saved metric name - we can't extract columns from it
       return;
@@ -181,7 +210,7 @@ export function extractColumnsFromSlice(slice: Slice): 
Set<string> {
       const metricColumn = metric.column;
       if (typeof metricColumn === 'string') {
         columns.add(metricColumn);
-      } else if (metricColumn?.column_name) {
+      } else if (metricColumn && typeof metricColumn === 'object' && 
'column_name' in metricColumn) {
         columns.add(metricColumn.column_name);
       }
     }
@@ -189,12 +218,12 @@ export function extractColumnsFromSlice(slice: Slice): 
Set<string> {
 
   // Extract metric (singular) - used by pie charts and other single-metric 
charts
   if (formData.metric && typeof formData.metric === 'object') {
-    const metric = formData.metric as any;
-    if ('column' in metric) {
+    const metric = formData.metric as FormDataMetric;
+    if ('column' in metric && metric.column) {
       const metricColumn = metric.column;
       if (typeof metricColumn === 'string') {
         columns.add(metricColumn);
-      } else if (metricColumn?.column_name) {
+      } else if (typeof metricColumn === 'object' && 'column_name' in 
metricColumn) {
         columns.add(metricColumn.column_name);
       }
     }
@@ -211,7 +240,7 @@ export function extractColumnsFromSlice(slice: Slice): 
Set<string> {
   }
 
   // Extract columns from filters
-  ensureIsArray(formData.adhoc_filters).forEach((filter: any) => {
+  ensureIsArray(formData.adhoc_filters).forEach((filter: FormDataFilter) => {
     if (filter?.subject && typeof filter.subject === 'string') {
       columns.add(filter.subject);
     }
@@ -327,3 +356,24 @@ export function sliceUsesColumn(slice: Slice, columnName: 
string): boolean {
 
   return false;
 }
+
+/**
+ * Format a multiplier value as a percentage change string.
+ * Example: 1.15 -> "+15%", 0.85 -> "-15%"
+ *
+ * @param multiplier - The multiplier value (1 = no change)
+ * @param decimals - Number of decimal places (default: 0)
+ * @returns Formatted percentage string with sign
+ */
+export function formatPercentageChange(
+  multiplier: number,
+  decimals: number = 0,
+): string {
+  const percentChange = (multiplier - 1) * 100;
+  const sign = percentChange >= 0 ? '+' : '';
+  const formatted =
+    decimals > 0
+      ? percentChange.toFixed(decimals)
+      : Math.round(percentChange).toString();
+  return `${sign}${formatted}%`;
+}


Reply via email to