🐛 Report the effective UI theme to plugins (#10677)

Penpot itself and plugins used different default theme selection
strategies when no theme is explicitly selected in Penpot by the user:

- Penpot itself defaults to dark.
- Plugins defaulted to whatever is configured in the user's
  system/browser, which could be light.

So with no explicit theme chosen in the profile — the common state —
every plugin could be told the theme was light while Penpot itself
showed dark, both on open (penpot.theme) and on themechange. For every
explicit choice (light, dark, system, and the legacy default) the two
resolutions already agreed; only the unset case diverged.

Extract the app's resolution into app.util.theme/resolve-theme (system
follows the system theme; default and unset mean dark) as the single
source of truth, and use it in the app's own use-initialize as well as
in the plugin runtime's getTheme and themechange handling. The
themechange handler's now-dead default-to-dark remapping is removed.

Fixes #10676

AI-assisted-by: claude-fable-5
This commit is contained in:
Dr. Dominik Jain 2026-07-28 11:09:17 +02:00 committed by GitHub
parent 7eb188b077
commit af00860c13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 16 additions and 27 deletions

View File

@ -237,15 +237,7 @@
:getTheme
(fn []
(let [theme (get-in @st/state [:profile :theme])]
(cond
(or (not theme) (= theme "system"))
(theme/get-system-theme)
(= theme "default")
"dark"
:else
theme)))
(theme/resolve-theme theme (theme/get-system-theme))))
:getCurrentUser
(fn []

View File

@ -53,16 +53,8 @@
(defn- get-theme
[state]
(let [theme (get-in state [:profile :theme])]
(cond
(or (not theme) (= theme "system"))
(theme/get-system-theme)
(= theme "default")
"dark"
:else
theme)))
(theme/resolve-theme (get-in state [:profile :theme])
(theme/get-system-theme)))
(defmethod handle-state-change "themechange"
[_ _ old-val new-val _]
@ -70,9 +62,7 @@
new-theme (get-theme new-val)]
(if (identical? old-theme new-theme)
::not-changed
(if (= new-theme "default")
"dark"
new-theme))))
new-theme)))
(defmethod handle-state-change "shapechange"
[_ plugin-id old-val new-val props]

View File

@ -45,6 +45,17 @@
(.removeAttribute node "class")
(.add ^js (.-classList ^js node) class)))
(defn resolve-theme
"Resolves the profile's theme setting to the effective UI theme ('dark' or
'light'): 'system' follows the given system theme, 'default' and an unset
theme mean dark, and any other value is taken as-is. Single source of truth
for the app's own theme and the theme reported to plugins."
[profile-theme system-theme]
(cond
(= profile-theme "system") system-theme
(= profile-theme "default") "dark"
:else (d/nilv profile-theme "dark")))
(defn use-initialize
[{profile-theme :theme}]
(let [system-theme* (mf/use-state get-system-theme)
@ -58,9 +69,5 @@
(rx/dispose! s))))
(mf/with-effect [system-theme profile-theme]
(set-color-scheme
(cond
(= profile-theme "system") system-theme
(= profile-theme "default") "dark"
:else (d/nilv profile-theme "dark")))
(set-color-scheme (resolve-theme profile-theme system-theme))
(notify-color-scheme-listeners!))))