korbit-ai[bot] commented on code in PR #35220:
URL: https://github.com/apache/superset/pull/35220#discussion_r2365266359


##########
superset-frontend/src/theme/ThemeController.ts:
##########
@@ -589,48 +669,81 @@ export class ThemeController {
    */
   private getThemeForMode(mode: ThemeMode): AnyThemeConfig | null {
     // Priority 1: Dev theme override (highest priority for development)
-    // Dev overrides affect all contexts
+    // Dev overrides affect all contexts but still merge with base
     if (this.devThemeOverride) {
-      return this.devThemeOverride;
+      const normalizedOverride = this.normalizeTheme(this.devThemeOverride);
+      const isDarkMode = this.isThemeDark(normalizedOverride);
+      const baseTheme = isDarkMode ? this.baseThemeDark : 
this.baseThemeDefault;
+
+      if (baseTheme) {
+        const mergedTheme = Theme.fromConfig(normalizedOverride, baseTheme);
+        return mergedTheme.toSerializedConfig();
+      }
+      return normalizedOverride;
     }
 
     // Priority 2: System theme based on mode (applies to all contexts)
     let resolvedMode: ThemeMode = mode;
 
     if (mode === ThemeMode.SYSTEM) {
-      // OS preference is allowed when dark theme exists
-      if (this.darkTheme === null) return null;
+      // OS preference is allowed when dark theme exists or base dark theme 
exists
+      if (this.darkTheme === null && this.baseThemeDark === null) return null;
       resolvedMode = ThemeController.getSystemPreferredMode();
     }
 
-    if (!this.hasCustomThemes) {
-      const baseTheme = this.defaultTheme.token as Partial<SupersetTheme>;
-      return getAntdConfig(baseTheme, resolvedMode === ThemeMode.DARK);
+    // Get the appropriate base theme for the mode
+    let baseTheme: AnyThemeConfig | null = null;
+    let customTheme: AnyThemeConfig | null = null;
+
+    if (resolvedMode === ThemeMode.DARK) {
+      baseTheme = this.baseThemeDark || this.baseThemeDefault;
+      customTheme = this.darkTheme;
+    } else {
+      baseTheme = this.baseThemeDefault;
+      customTheme = this.defaultTheme;
+    }

Review Comment:
   ### Asymmetric base theme fallback logic <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Dark mode fallback logic uses default base theme when base dark theme is 
missing, but light mode doesn't have equivalent fallback to base dark theme.
   
   
   ###### Why this matters
   This asymmetric fallback behavior could cause inconsistent theming where 
dark mode gets a fallback but light mode doesn't, potentially leading to broken 
themes in edge cases.
   
   ###### Suggested change ∙ *Feature Preview*
   Make the fallback logic symmetric:
   ```typescript
   if (resolvedMode === ThemeMode.DARK) {
     baseTheme = this.baseThemeDark || this.baseThemeDefault;
     customTheme = this.darkTheme;
   } else {
     baseTheme = this.baseThemeDefault || this.baseThemeDark;
     customTheme = this.defaultTheme;
   }
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/e2cb8608-838a-476d-8da9-2de476700db2/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/e2cb8608-838a-476d-8da9-2de476700db2?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/e2cb8608-838a-476d-8da9-2de476700db2?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/e2cb8608-838a-476d-8da9-2de476700db2?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/e2cb8608-838a-476d-8da9-2de476700db2)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:1c932f49-a25b-4088-a4b3-dc942c71ba1a -->
   
   
   [](1c932f49-a25b-4088-a4b3-dc942c71ba1a)



##########
superset/views/base.py:
##########
@@ -348,34 +350,74 @@ def get_theme_bootstrap_data() -> dict[str, Any]:
                     "Invalid JSON in system dark theme %s", dark_theme_model.id
                 )
                 # Fallback to config
-                dark_theme = get_config_value("THEME_DARK")
+                dark_theme = config_theme_dark
         else:
             # No system dark theme in database, use config
-            dark_theme = get_config_value("THEME_DARK")
+            dark_theme = config_theme_dark
     else:
-        # UI theme administration disabled, use config-based themes
-        default_theme = get_config_value("THEME_DEFAULT")
-        dark_theme = get_config_value("THEME_DARK")
-
-    # Validate theme configurations
-    if not is_valid_theme(default_theme):
+        # UI theme administration disabled
+        # Use config-based themes
+        default_theme = config_theme_default
+        dark_theme = config_theme_dark
+
+    # Get base themes from config (with fallback for backwards compatibility)
+    base_theme = app.config.get("BASE_THEME_DEFAULT", None)
+    base_theme_dark = app.config.get("BASE_THEME_DARK", None)
+
+    # Determine if themes need to fall back to base themes
+    # A theme needs fallback if it's None or empty
+    def should_use_base(theme):
+        """Check if a theme should be replaced with base theme entirely"""
+        if theme is None or theme == {}:
+            return True
+
+        return False
+
+    # Handle default theme fallback
+    if should_use_base(default_theme):
+        # When config theme is None, don't provide a custom theme
+        # The frontend will use base theme only
+        default_theme = {}
+    elif not is_valid_theme(default_theme):
         logger.warning(
-            "Invalid default theme configuration: %s, using empty theme",
+            "Invalid default theme configuration: %s, clearing it",
             default_theme,
         )
         default_theme = {}
 
-    if not is_valid_theme(dark_theme):
+    # Handle dark theme fallback
+    if should_use_base(dark_theme):
+        # When config theme is None, don't provide a custom theme
+        # The frontend will use base dark theme only
+        dark_theme = {}
+    elif not is_valid_theme(dark_theme):
         logger.warning(
-            "Invalid dark theme configuration: %s, using empty theme",
+            "Invalid dark theme configuration: %s, clearing it",
             dark_theme,
         )
         dark_theme = {}
 
+    # Validate base theme configurations
+    if base_theme is not None and not is_valid_theme(base_theme):
+        logger.warning(
+            "Invalid base theme configuration: %s, ignoring",
+            base_theme,
+        )
+        base_theme = None
+
+    if base_theme_dark is not None and not is_valid_theme(base_theme_dark):
+        logger.warning(
+            "Invalid base dark theme configuration: %s, ignoring",
+            base_theme_dark,
+        )
+        base_theme_dark = None

Review Comment:
   ### Duplicated validation logic <sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Duplicate validation logic for base themes that could be consolidated into a 
reusable pattern.
   
   
   ###### Why this matters
   The same validation pattern is repeated for both base themes, leading to 
code duplication and potential maintenance overhead. While not a major 
performance issue, it represents inefficient code organization.
   
   ###### Suggested change ∙ *Feature Preview*
   Create a helper function or use a loop to validate both themes:
   ```python
   for theme_name, theme_var in [("base theme", base_theme), ("base dark 
theme", base_theme_dark)]:
       if theme_var is not None and not is_valid_theme(theme_var):
           logger.warning("Invalid %s configuration: %s, ignoring", theme_name, 
theme_var)
           if theme_name == "base theme":
               base_theme = None
           else:
               base_theme_dark = None
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f3663fbe-aa8d-401d-8140-9f89dee54ab8/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f3663fbe-aa8d-401d-8140-9f89dee54ab8?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f3663fbe-aa8d-401d-8140-9f89dee54ab8?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f3663fbe-aa8d-401d-8140-9f89dee54ab8?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f3663fbe-aa8d-401d-8140-9f89dee54ab8)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:f0d51401-d22c-4d54-8a25-0fe2f6fe52f8 -->
   
   
   [](f0d51401-d22c-4d54-8a25-0fe2f6fe52f8)



##########
superset-frontend/packages/superset-ui-core/src/theme/Theme.tsx:
##########
@@ -118,9 +53,48 @@ export class Theme {
    * If simple tokens are provided as { token: {...} }, they will be applied 
with defaults
    * If no config is provided, uses default tokens
    * Dark mode can be set via the algorithm property in the config
+   * @param config - The theme configuration
+   * @param baseTheme - Optional base theme to apply under the config
    */
-  static fromConfig(config?: AnyThemeConfig): Theme {
-    return new Theme({ config });
+  static fromConfig(
+    config?: AnyThemeConfig,
+    baseTheme?: AnyThemeConfig,
+  ): Theme {
+    let mergedConfig: AnyThemeConfig | undefined = config;
+
+    if (baseTheme && config) {
+      mergedConfig = {
+        ...baseTheme,
+        ...config,
+      } as AnyThemeConfig;
+
+      if (baseTheme.token || config.token) {
+        (mergedConfig as any).token = {
+          ...(baseTheme.token || {}),
+          ...(config.token || {}),
+        };
+      }

Review Comment:
   ### Complex Theme Merging Logic <sub>![category 
Design](https://img.shields.io/badge/Design-0d9488)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The theme merging logic in fromConfig is complex and uses type assertions, 
making it difficult to maintain and type-unsafe.
   
   
   ###### Why this matters
   Complex merging logic embedded in the static factory method makes the code 
harder to test, maintain, and increases the likelihood of bugs when handling 
theme configurations.
   
   ###### Suggested change ∙ *Feature Preview*
   ```typescript
   private static mergeThemeConfigs(base?: AnyThemeConfig, override?: 
AnyThemeConfig): AnyThemeConfig {
       if (!base) return override || {};
       if (!override) return base;
       
       return {
           ...base,
           ...override,
           token: {
               ...(base.token || {}),
               ...(override.token || {})
           },
           components: this.mergeComponents(base.components, 
override.components)
       };
   }
   
   static fromConfig(config?: AnyThemeConfig, baseTheme?: AnyThemeConfig): 
Theme {
       const mergedConfig = this.mergeThemeConfigs(baseTheme, config);
       return new Theme({ config: mergedConfig });
   }
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3dc4bef7-7b88-40d8-a654-281d6fd779fb/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3dc4bef7-7b88-40d8-a654-281d6fd779fb?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3dc4bef7-7b88-40d8-a654-281d6fd779fb?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3dc4bef7-7b88-40d8-a654-281d6fd779fb?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3dc4bef7-7b88-40d8-a654-281d6fd779fb)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:b26beb71-3e06-4bea-9042-f62aaad443d8 -->
   
   
   [](b26beb71-3e06-4bea-9042-f62aaad443d8)



##########
superset-frontend/packages/superset-ui-core/src/theme/GlobalStyles.tsx:
##########
@@ -16,6 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
+// @fontsource/* v5.1+ doesn't play nice with eslint-import plugin v2.31+
+/* eslint-disable import/extensions */
+import '@fontsource/inter/200.css';
+import '@fontsource/inter/400.css';
+import '@fontsource/inter/500.css';
+import '@fontsource/inter/600.css';
+import '@fontsource/fira-code/400.css';
+import '@fontsource/fira-code/500.css';
+import '@fontsource/fira-code/600.css';

Review Comment:
   ### Blocking font CSS imports delay rendering <sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Multiple individual CSS font imports are loaded synchronously at module 
level, causing blocking network requests during initial bundle parsing.
   
   
   ###### Why this matters
   This creates a waterfall of blocking CSS requests that delay the critical 
rendering path and increase Time to First Contentful Paint, especially on 
slower networks.
   
   ###### Suggested change ∙ *Feature Preview*
   Use dynamic imports with `import()` to load fonts asynchronously, or combine 
font weights into fewer CSS files, or use font-display: swap in the CSS to 
prevent render blocking:
   
   ```typescript
   // Load fonts asynchronously
   const loadFonts = async () => {
     await Promise.all([
       import('@fontsource/inter/200.css'),
       import('@fontsource/inter/400.css'),
       import('@fontsource/inter/500.css'),
       import('@fontsource/inter/600.css'),
       import('@fontsource/fira-code/400.css'),
       import('@fontsource/fira-code/500.css'),
       import('@fontsource/fira-code/600.css')
     ]);
   };
   
   // Call in useEffect or during app initialization
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f46cf04e-2239-425e-9a75-80f41a77f287/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f46cf04e-2239-425e-9a75-80f41a77f287?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f46cf04e-2239-425e-9a75-80f41a77f287?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f46cf04e-2239-425e-9a75-80f41a77f287?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/f46cf04e-2239-425e-9a75-80f41a77f287)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:8017a30a-b561-4290-9ee9-aebb7412c367 -->
   
   
   [](8017a30a-b561-4290-9ee9-aebb7412c367)



##########
superset-frontend/src/theme/ThemeController.ts:
##########
@@ -589,48 +669,81 @@ export class ThemeController {
    */
   private getThemeForMode(mode: ThemeMode): AnyThemeConfig | null {
     // Priority 1: Dev theme override (highest priority for development)
-    // Dev overrides affect all contexts
+    // Dev overrides affect all contexts but still merge with base
     if (this.devThemeOverride) {
-      return this.devThemeOverride;
+      const normalizedOverride = this.normalizeTheme(this.devThemeOverride);
+      const isDarkMode = this.isThemeDark(normalizedOverride);
+      const baseTheme = isDarkMode ? this.baseThemeDark : 
this.baseThemeDefault;
+
+      if (baseTheme) {
+        const mergedTheme = Theme.fromConfig(normalizedOverride, baseTheme);
+        return mergedTheme.toSerializedConfig();
+      }
+      return normalizedOverride;
     }
 
     // Priority 2: System theme based on mode (applies to all contexts)
     let resolvedMode: ThemeMode = mode;
 
     if (mode === ThemeMode.SYSTEM) {
-      // OS preference is allowed when dark theme exists
-      if (this.darkTheme === null) return null;
+      // OS preference is allowed when dark theme exists or base dark theme 
exists
+      if (this.darkTheme === null && this.baseThemeDark === null) return null;
       resolvedMode = ThemeController.getSystemPreferredMode();
     }
 
-    if (!this.hasCustomThemes) {
-      const baseTheme = this.defaultTheme.token as Partial<SupersetTheme>;
-      return getAntdConfig(baseTheme, resolvedMode === ThemeMode.DARK);
+    // Get the appropriate base theme for the mode
+    let baseTheme: AnyThemeConfig | null = null;
+    let customTheme: AnyThemeConfig | null = null;

Review Comment:
   ### Complex theme resolution logic needs strategy pattern <sub>![category 
Design](https://img.shields.io/badge/Design-0d9488)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The getThemeForMode method is complex with multiple levels of theme 
resolution logic and conditional branches, making it difficult to understand 
and maintain.
   
   
   ###### Why this matters
   Complex methods with multiple responsibilities and decision paths are prone 
to bugs and make it harder to modify theme resolution logic in the future.
   
   ###### Suggested change ∙ *Feature Preview*
   Implement a Strategy pattern for theme resolution:
   ```typescript
   interface ThemeResolutionStrategy {
     resolveTheme(mode: ThemeMode): AnyThemeConfig | null;
   }
   
   class DevOverrideStrategy implements ThemeResolutionStrategy { ... }
   class SystemThemeStrategy implements ThemeResolutionStrategy { ... }
   class BaseThemeStrategy implements ThemeResolutionStrategy { ... }
   
   class ThemeResolver {
     constructor(private strategies: ThemeResolutionStrategy[]) {}
     resolve(mode: ThemeMode): AnyThemeConfig | null {
       for (const strategy of this.strategies) {
         const theme = strategy.resolveTheme(mode);
         if (theme) return theme;
       }
       return null;
     }
   }
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/d0791393-100d-4650-a13a-b11e6a4dd5e9/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/d0791393-100d-4650-a13a-b11e6a4dd5e9?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/d0791393-100d-4650-a13a-b11e6a4dd5e9?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/d0791393-100d-4650-a13a-b11e6a4dd5e9?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/d0791393-100d-4650-a13a-b11e6a4dd5e9)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:284b60c0-6386-4b2d-9fd3-e4200dca818c -->
   
   
   [](284b60c0-6386-4b2d-9fd3-e4200dca818c)



##########
superset/config.py:
##########
@@ -723,46 +723,88 @@ class D3TimeFormat(TypedDict, total=False):
 # This is merely a default
 EXTRA_CATEGORICAL_COLOR_SCHEMES: list[dict[str, Any]] = []
 
-# ---------------------------------------------------
-# Theme Configuration for Superset
-# ---------------------------------------------------
+# -----------------------------------------------------------------------------
+# Theme System Configuration
+# -----------------------------------------------------------------------------
 # Superset supports custom theming through Ant Design's theme structure.
-# This allows users to customize colors, fonts, and other UI elements.
 #
-# Theme Generation:
-# - Use the Ant Design theme editor: https://ant.design/theme-editor
-# - Export or copy the generated theme JSON and assign to the variables below
-# - For detailed instructions: 
https://superset.apache.org/docs/configuration/theming/
+# Theme Hierarchy (layers apply in order, each overriding previous):
+# 1. BASE_THEME_DEFAULT/BASE_THEME_DARK - Foundation tokens ALWAYS present
+# 2. THEME_DEFAULT/THEME_DARK - Config themes (overlay on BASE themes)
+# 3. System CRUD themes (when ENABLE_UI_THEME_ADMINISTRATION=True) - Set via UI
+# 4. Dashboard CRUD themes - Applied locally per dashboard with bolt button
 #
-# To expose a JSON theme editor modal that can be triggered from the navbar
-# set the `ENABLE_THEME_EDITOR` feature flag to True.
+# Theme Merging:
+# - Base themes are ALWAYS the foundation - never removed, only overridden
+# - Each layer only adds/modifies specific tokens, preserving all others
+# - Empty theme {} or None means "no custom tokens at this layer"
+# - All themes are merged: base + config overlays + system overlays + 
dashboard overlays
 #
-# Theme Structure:
-# Each theme should follow Ant Design's theme format.
-# To create custom themes, use the Ant Design Theme Editor at 
https://ant.design/theme-editor
-# and copy the generated JSON configuration.
+# System Theme Behavior (UI Administration):
+# - Only themes explicitly marked as system (via moon/sun buttons) are used
+# - Removing a system theme falls back to config theme (or base if config is 
None)
+# - No automatic fallback to other themes in the database
 #
-# Example theme definition:
-# THEME_DEFAULT = {
-#       "token": {
-#            "colorPrimary": "#2893B3",
-#            "colorSuccess": "#5ac189",
-#            "colorWarning": "#fcc700",
-#            "colorError": "#e04355",
-#            "fontFamily": "'Inter', Helvetica, Arial",
-#            ... # other tokens
-#       },
-#       ... # other theme properties
-# }
+# Theme Generation:
+# - Use the Ant Design theme editor: https://ant.design/theme-editor
+# - Export the generated JSON and assign to the theme variables
+# - To expose a JSON theme editor modal in the UI, set ENABLE_THEME_EDITOR 
feature flag to True
+#
+# Example: If BASE_THEME_DEFAULT sets colorPrimary: "#2893B3" and a custom 
theme sets
+# colorPrimary: "#FF0000", the final theme will have colorPrimary: "#FF0000"
+# -----------------------------------------------------------------------------
+
+# Base theme with Superset's default tokens (foundation for all themes)
+BASE_THEME_DEFAULT: Theme = {
+    "token": {
+        # Brand
+        "brandLogoAlt": "Apache Superset",
+        "brandLogoUrl": "/static/assets/images/superset-logo-horiz.png",
+        "brandLogoMargin": "18px",
+        "brandLogoHref": "/",
+        "brandLogoHeight": "24px",
+        # Spinner
+        "brandSpinnerUrl": None,
+        "brandSpinnerSvg": None,
+        # Default colors
+        "colorPrimary": "#2893B3",  # NOTE: previous lighter primary color was 
#20a7c9 # noqa: E501
+        "colorLink": "#2893B3",
+        "colorError": "#e04355",
+        "colorWarning": "#fcc700",
+        "colorSuccess": "#5ac189",
+        "colorInfo": "#66bcfe",
+        # Fonts
+        "fontFamily": "'Inter', Helvetica, Arial",
+        "fontFamilyCode": "'Fira Code', 'Courier New', monospace",
+        # Extra tokens
+        "transitionTiming": 0.3,
+        "brandIconMaxWidth": 37,
+        "fontSizeXS": "8",
+        "fontSizeXXL": "28",
+        "fontWeightNormal": "400",
+        "fontWeightLight": "300",
+        "fontWeightStrong": 500,
+    },
+    "algorithm": "default",
+}
 
+# Base dark theme configuration - foundation tokens for dark mode
+# Inherits all tokens from BASE_THEME_DEFAULT and adds dark algorithm
+# This ensures dark mode gets the correct algorithm even if THEME_DARK is empty
+BASE_THEME_DARK: Theme = {
+    **BASE_THEME_DEFAULT,
+    "algorithm": "dark",
+}

Review Comment:
   ### Shared mutable token dictionary between base themes <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The BASE_THEME_DARK theme uses dictionary unpacking (**BASE_THEME_DEFAULT) 
which creates a shallow copy, but then overwrites the 'algorithm' key. This 
means both themes share the same nested 'token' dictionary object in memory.
   
   
   ###### Why this matters
   When any code modifies the 'token' dictionary in either BASE_THEME_DEFAULT 
or BASE_THEME_DARK, it will affect both themes simultaneously since they 
reference the same object. This could lead to unexpected theme corruption where 
changes to one theme accidentally modify the other.
   
   ###### Suggested change ∙ *Feature Preview*
   Use deep copy to ensure each theme has its own independent token dictionary:
   
   ```python
   import copy
   
   BASE_THEME_DARK: Theme = {
       **copy.deepcopy(BASE_THEME_DEFAULT),
       "algorithm": "dark",
   }
   ```
   
   Alternatively, restructure to avoid the shared reference:
   
   ```python
   _BASE_THEME_TOKENS = {
       # Brand
       "brandLogoAlt": "Apache Superset",
       # ... all token definitions
   }
   
   BASE_THEME_DEFAULT: Theme = {
       "token": _BASE_THEME_TOKENS.copy(),
       "algorithm": "default",
   }
   
   BASE_THEME_DARK: Theme = {
       "token": _BASE_THEME_TOKENS.copy(),
       "algorithm": "dark",
   }
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/55ecfabe-e02f-41fd-b8b3-b420ece150c3/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/55ecfabe-e02f-41fd-b8b3-b420ece150c3?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/55ecfabe-e02f-41fd-b8b3-b420ece150c3?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/55ecfabe-e02f-41fd-b8b3-b420ece150c3?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/55ecfabe-e02f-41fd-b8b3-b420ece150c3)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:0809613f-4a86-4b6c-9570-b98ad150d5b3 -->
   
   
   [](0809613f-4a86-4b6c-9570-b98ad150d5b3)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to