diff --git a/src/main/frontend/components/icon.cljs b/src/main/frontend/components/icon.cljs index fe586cbe6b..f8e9c3be91 100644 --- a/src/main/frontend/components/icon.cljs +++ b/src/main/frontend/components/icon.cljs @@ -478,6 +478,15 @@ ;; the design rationale (subscribing to the per-tx atom is ;; what catches retractions; `model/sub-block` doesn't). _latest-tx (state/sub :db/latest-transacted-entity-uuids) + ;; Re-render on theme toggle so `avatar-fallback-style` re- + ;; samples `--ls-primary-background-color` from the new + ;; cascade. The avatar's inline `style` is computed in JS + ;; from a `getComputedStyle` snapshot — without a Rum + ;; subscription on `:ui/theme`, the snapshot stays frozen + ;; after a theme toggle (state.cljs:1283) and the avatar + ;; renders with the previous theme's surface tone until + ;; some unrelated re-render fires. + _theme (state/sub :ui/theme) asset-entity (when (and asset-uuid (string? asset-uuid)) (try (db/entity [:block/uuid (uuid asset-uuid)]) (catch :default _ nil))) @@ -893,7 +902,16 @@ (rum/defc get-node-icon-cp < rum/reactive db-mixins/query [node-entity opts] - (let [;; Get fresh entity using db/sub-block to make it reactive to property changes + (let [;; Re-render on theme toggle so the bare-avatar branch inside + ;; `(icon ...)` (text/letters/emoji avatars) re-samples the + ;; live `--ls-primary-background-color` via `read-bg-var` + ;; when computing `avatar-fallback-style`. Without this sub, + ;; the inline `style` hexes baked in at the previous render + ;; stay frozen across theme toggles — none of the other + ;; subscriptions in this component (`:ui/icon-hover-preview`, + ;; `model/sub-block`) tick on `:ui/theme` changes. + _theme (state/sub :ui/theme) + ;; Get fresh entity using db/sub-block to make it reactive to property changes fresh-entity (when-let [db-id (:db/id node-entity)] (or (model/sub-block db-id) node-entity)) entity (or fresh-entity node-entity) diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index 2298f508fe..73b986bb43 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -1244,6 +1244,29 @@ Similar to re-frame subscriptions" (set-edit-content! edit-input-id content) (set-editor-last-pos! new-pos))) +(defn- apply-theme-to-dom! + "Synchronously stamp `data-theme` + body classes onto the document + so CSS variables re-resolve to the new theme *before* the state + mutation below triggers React subscribers. Without this, subscribers + that compute hex values from CSS vars at render time (e.g. avatar + fallback colors via `colors/read-bg-var` → `getComputedStyle`) + would re-render with the OLD theme's resolved vars because + `theme.cljs`'s `use-effect!` only sets these attributes *after* + the render commit. That effect remains as a safety net (it's + idempotent — same setAttribute) and continues to handle the + plugin-hook + custom-theme side effects." + [mode] + (when (exists? js/document) + (let [^js doc js/document.documentElement + ^js cls (.-classList doc) + ^js cls-body (.-classList js/document.body)] + (.setAttribute doc "data-theme" mode) + (if (= mode "dark") + (do (.add cls "dark") + (doto cls-body (.remove "white-theme" "light-theme") (.add "dark-theme"))) + (do (.remove cls "dark") + (doto cls-body (.remove "dark-theme") (.add "white-theme" "light-theme"))))))) + (defn set-theme-mode! ([mode] (set-theme-mode! mode (:ui/system-theme? @state))) ([mode system-theme?] @@ -1253,6 +1276,7 @@ Similar to re-frame subscriptions" (util/set-theme-dark))) (when (mobile-util/native-platform?) (mobile-util/set-native-interface-style! mode system-theme?)) + (apply-theme-to-dom! mode) (set-state! :ui/theme mode) (storage/set :ui/theme mode)))