Repository: reflex-dev/reflex
Pull request: [REF-2588]Clear color mode local storage for dev mode
Sometimes an app's `appearance` value(`rx.App(theme=rx.theme(appearance="dark"))`) does not persist correctly in the local storage(as `chakra-ui-color-mode`). Eg, if you manipulate the local storage value and restart the app, the app's theme/color-mode is not respected. This is typically annoying in dev mode. 
We want to clear this local storage key(`chakra-ui-color-mode`) when the app is started or when the app is hot reloaded, in this case, the app's theme/appearance/color-mode will be persisted and respected.

We do this by keeping a timestamp of the last time the app was compiled and saving this value in the local storage. For cases where the local storage value of the timestamp is any other than what is generated in `context.js` at the time of compilation, we clear the local storage key(of `chakra-ui-color-mode`).

closes #2047 #3548

Problem statement:
Dev Mode Clear Dark Cookie
During dev mode sometimes when you are on another app and running dark mode stop that app and go to a new one the cookie for local host is cached, and your other app has dark mode. On every run dev, we should clear this cookie

Hints from issue discussion:

Commit message: fix CI

Patch to review:
diff --git a/integration/test_client_storage.py b/integration/test_client_storage.py
index 393ea104410..03dbd168147 100644
--- a/integration/test_client_storage.py
+++ b/integration/test_client_storage.py
@@ -311,6 +311,7 @@ def set_sub_sub(var: str, value: str):
     assert not driver.get_cookies()
     local_storage_items = local_storage.items()
     local_storage_items.pop("chakra-ui-color-mode", None)
+    local_storage_items.pop("last_compiled_time", None)
     assert not local_storage_items
 
     # set some cookies and local storage values
@@ -426,6 +427,7 @@ def set_sub_sub(var: str, value: str):
 
     local_storage_items = local_storage.items()
     local_storage_items.pop("chakra-ui-color-mode", None)
+    local_storage_items.pop("last_compiled_time", None)
     assert (
         local_storage_items.pop("state.client_side_state.client_side_sub_state.l1")
         == "l1 value"
diff --git a/reflex/.templates/jinja/web/pages/_app.js.jinja2 b/reflex/.templates/jinja/web/pages/_app.js.jinja2
index 832bb61fb64..d4322b171e2 100644
--- a/reflex/.templates/jinja/web/pages/_app.js.jinja2
+++ b/reflex/.templates/jinja/web/pages/_app.js.jinja2
@@ -8,7 +8,6 @@ import '/styles/styles.css'
 import { EventLoopProvider, StateProvider, defaultColorMode } from "/utils/context.js";
 import { ThemeProvider } from 'next-themes'
 
-
 {% for custom_code in custom_codes %}
 {{custom_code}}
 {% endfor %}
diff --git a/reflex/.templates/jinja/web/utils/context.js.jinja2 b/reflex/.templates/jinja/web/utils/context.js.jinja2
index 95e9fb3ab01..caee4e0cac1 100644
--- a/reflex/.templates/jinja/web/utils/context.js.jinja2
+++ b/reflex/.templates/jinja/web/utils/context.js.jinja2
@@ -64,6 +64,8 @@ export const initialEvents = () => []
 
 export const isDevMode = {{ is_dev_mode|json_dumps }}
 
+export const lastCompiledTimeStamp = {{ last_compiled_time|json_dumps }}
+
 export function UploadFilesProvider({ children }) {
   const [filesById, setFilesById] = useState({})
   refs["__clear_selected_files"] = (id) => setFilesById(filesById => {
diff --git a/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js b/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js
index 1756c9101d9..d16e4e30ad8 100644
--- a/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js
+++ b/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js
@@ -1,6 +1,11 @@
 import { useTheme } from "next-themes";
 import { useEffect, useState } from "react";
-import { ColorModeContext, defaultColorMode } from "/utils/context.js";
+import {
+  ColorModeContext,
+  defaultColorMode,
+  isDevMode,
+} from "/utils/context.js";
+import { lastCompiledTimeStamp } from "/utils/context.js";
 
 export default function RadixThemesColorModeProvider({ children }) {
   const { theme, resolvedTheme, setTheme } = useTheme();
@@ -8,6 +13,19 @@ export default function RadixThemesColorModeProvider({ children }) {
   const [resolvedColorMode, setResolvedColorMode] = useState("dark");
 
   useEffect(() => {
+    if (isDevMode) {
+      const lastCompiledTimeInLocalStorage =
+        localStorage.getItem("last_compiled_time");
+      if (
+        lastCompiledTimeInLocalStorage &&
+        lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp
+      ) {
+        // on app startup, make sure the application color mode is persisted correctly.
+        setTheme(defaultColorMode);
+        localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
+        return;
+      }
+    }
     setRawColorMode(theme);
     setResolvedColorMode(resolvedTheme);
   }, [theme, resolvedTheme]);
@@ -19,7 +37,7 @@ export default function RadixThemesColorModeProvider({ children }) {
     const allowedModes = ["light", "dark", "system"];
     if (!allowedModes.includes(mode)) {
       console.error(
-        `Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".`
+        `Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".`,
       );
       mode = defaultColorMode;
     }
diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py
index 002c3b75058..4345e244ff4 100644
--- a/reflex/compiler/compiler.py
+++ b/reflex/compiler/compiler.py
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 import os
+from datetime import datetime
 from pathlib import Path
 from typing import Dict, Iterable, Optional, Type, Union
 
@@ -79,20 +80,24 @@ def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None)
         The compiled context file.
     """
     appearance = getattr(theme, "appearance", None)
-    if appearance is None:
+    if appearance is None or Var.create_safe(appearance)._var_name == "inherit":
         appearance = SYSTEM_COLOR_MODE
+
+    last_compiled_time = str(datetime.now())
     return (
         templates.CONTEXT.render(
             initial_state=utils.compile_state(state),
             state_name=state.get_name(),
             client_storage=utils.compile_client_storage(state),
             is_dev_mode=not is_prod_mode(),
+            last_compiled_time=last_compiled_time,
             default_color_mode=appearance,
         )
         if state
         else templates.CONTEXT.render(
             is_dev_mode=not is_prod_mode(),
             default_color_mode=appearance,
+            last_compiled_time=last_compiled_time,
         )
     )
 


Code review comments:
diff_hunk: @@ -1,13 +1,31 @@
 import { useTheme } from "next-themes";
 import { useEffect, useState } from "react";
-import { ColorModeContext, defaultColorMode } from "/utils/context.js";
+import {
+  ColorModeContext,
+  defaultColorMode,
+  isDevMode,
+} from "/utils/context.js";
+import { lastCompiledTimeStamp } from "/utils/context.js";
path: reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js
text: @user1:
i think this can go in the import list in the line above