Apply preset custom uiTheme colors on first load - #3210
Conversation
A custom theme passed via customization.uiTheme is registered by id, but its colors (loaded async from themes.json) were only written to CSS by apply_theme, which runs solely on runtime selection. On a fresh load the editor fell back to the base dark/light theme. - check_launched_custom_theme now writes the launched theme's colors once, the first time it resolves, mirroring apply_theme. - create_colors_css emits a doubled-class selector so a custom theme's tokens out-specify the base :root .theme-type-dark rules instead of tying and losing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
|
Independent confirmation of this on ONLYOFFICE Desktop Editors (Linux, I hit the same bug from the other direction: a custom theme dropped into Hunk 1 ( Hunk 2 (write on launch) — confirmed. Before it, a cold start applies only the Writing the CSS is not sufficient on its ownSeveral consumers read theme state once, during init, which on a cold start happens before
const colors_obj = get_current_theme_colors();
if ( validate_vars(colors_obj) ) {
colors_obj.type = themes_map[theme_id].type;
colors_obj.name = theme_id;
this.api.asc_setSkin(colors_obj); // needs the api reference in scope here
}
Common.NotificationCenter.trigger('uitheme:changed', theme_id, 'native');( Two things I could not solve — noting them rather than claiming a fix1. A ~2 second window on document open where the UI is the base theme. Sampling frames from a 2. Scrollbars never take the theme's colours. Unlike the row/column headers, they are fixed Worth noting: I also tried writing the theme's variables directly into the shipped stylesheets, so Both are pre-existing and orthogonal to this PR — it makes things strictly better — but they are Happy to open a follow-up PR for the |
|
Follow-up from further testing of a custom dark Three findings below. (1) is a one-line fix in this repo; (2) and (3) are in the SDK, not web-apps, Everything below was measured against a running editor rather than read off the source: the desktop LD_LIBRARY_PATH=/opt/onlyoffice/desktopeditors /opt/onlyoffice/desktopeditors/DesktopEditors \
--no-sandbox --remote-debugging-port=9222
curl -s http://127.0.0.1:9222/json # the editor itself is an iframe execution context1. Sheet tab accent is a hardcoded colour with no
|
--canvas-scroll-thumb |
painted |
|---|---|
#313244 |
#313131 |
#585b70 (hover) |
#585858 |
#0000ff (injected probe) |
#000000 |
The probe rules this out as a blend or an opacity artefact, and it is not a luminance conversion
either — a luminance grey of #0000ff would be roughly #1d1d1d, not #000000.
Why this has stayed invisible upstream: every stock theme already uses pure greys here —
#f7f7f7, #c0c0c0, #cbcbcb (light) and #404040, #999, #616161 (dark). For any colour where
R == G == B the bug is a no-op. It only shows on a theme whose surfaces are tinted.
Two further notes that may help locate it:
- The same variable renders correctly in part of the same widget. With the
#0000ffprobe in
place, the scrollbar region contained 3,980 px of greyed#000000alongside 105 px of true
#0000FF, so the greying is one fill path, not a colour that arrived wrong. - This explains why neither a late
asc_setSkinnor a theme toggle ever fixed these, which I'd
previously assumed pointed at a timing problem: both re-push the same value down the same lossy
path, so they cannot help.
Because the loss is downstream of the theme, there is no value a uitheme can supply that renders
non-grey. It isn't workaroundable from a theme file.
3. Document dark mode paints a fixed page colour, ignoring the theme
With Advanced Settings → Appearance → Turn on document dark mode, the page renders #393939
regardless of the active UI theme — identical under stock theme-dark and under a custom dark theme.
Checks:
- No theme variable covers it. The only
--canvas-*dark*tokens in the shipped CSS are the six
--canvas-dark-cell-title*. --canvas-content-backgroundcontrols the page normally (#ffffff) but has no effect once dark
mode is on.- It isn't a stored constant: no
393939/3750201/(57,57,57)literal in
sdkjs/word/sdk-all-min.js, and a runtime walk ofAsc.editorandAscCommonto depth 3
(11,018 objects) found no#393939string and noCColor(57,57,57).
So a custom dark theme can style the whole UI and then hand off to a page background it has no say
over. Is exposing this as a theme token (or deriving it from --canvas-content-background /
--canvas-background) something you'd consider?
Minor, related: the checkbox is only rendered in the document editor's settings panel, but the value
is global — it's stored in localStorage["content-theme"] and both documenteditor/main/app.js and
spreadsheeteditor/main/app.js call asc_setContentDarkMode on their theme-apply path. That is
mildly confusing from the UI, since turning it on in Documents silently changes Spreadsheets too.
Not a bug, but worth documenting
Two behaviours cost a fair amount of time to work out, and neither is written down anywhere I could
find. If they're deliberate, a line in the theming docs would save the next person the trouble:
brand-word/brand-cell/brand-slide/brand-pdf/brand-draware the document tab's
fill, and the shell always draws that tab's label intext-inverse. Sobrand-*has to stay a
light colour in a dark theme; set it to a dark one and the filename becomes invisible. The stock
dark themes set all five to a flat near-black (#222222intheme-night) and still get a legible
light label, so they evidently don't take this path — which makes them misleading as a reference.- The native shell reads
brand-*and the other chrome keys straight from the JSON in
uithemes/, not from anything the web layer loads, so those keys respond to a different file
than the rest of the theme.
What problem does this solve?
A custom theme passed via
customization.uiThemedoesn't apply its colors on a fresh load. The editor renders the base dark/light theme instead, and the custom colors only appear once you re-pick the theme from the Interface Theme menu.Root cause: the theme arrives as an id only, and its
colors(loaded async fromthemes.json) are turned into CSS vars byapply_theme, which only runs on runtime selection. On preset load,check_launched_custom_themesets the body class but never writes the colors.How does this solve it?
Two changes in
apps/common/main/lib/controller/Themes.js:check_launched_custom_themenow writes the launched theme's colors once, the first time it resolves, mirroring whatapply_themedoes. So a preset theme behaves like a selected one.create_colors_cssemits a doubled-class selector (:root .id.id). A single:root .idonly ties the base:root .theme-type-dark { ... }rules and loses on cascade order, so a dark/light custom theme's tokens (e.g.--background-toolbar) silently fell back to the base value. This also fixes runtime selection of dark custom themes, not just preset load.How do I test this?
Tested locally against
onlyoffice/documentserver-de9.4.0 with a custom theme preset viauiTheme. Without the change the toolbar loads in the base theme; with it the custom colors apply on first paint. Reproduced the base-theme fallback as a control, then confirmed the fix renders the custom colors.