Skip to content

Commit 4a6a71f

Browse files
JohnMcLearclaude
andcommitted
fix(7606): sync theme-color meta with client-side dark-mode switch
PR #7636 emits <meta name="theme-color"> server-side from settings.skinVariants. That covers operators who hard-code a dark toolbar in settings.json, but not the runtime path: pad.ts auto-flips the toolbar to super-dark when enableDarkMode is on, the browser reports prefers-color-scheme: dark, and no localStorage white-mode override is set, plus the user can flip it via #options-darkmode. Both paths run skinVariants.updateSkinVariantsClasses(), which until now never touched the meta — so dark-mode users kept the light #ffffff baseline and saw a white address bar above a dark toolbar (stffen on #7606 after 2.7.3). Push the toolbar-color lookup into updateSkinVariantsClasses so the meta tracks every class change: the auto-switch on init, the user toggle, and the skinVariants builder. Mirrors the CSS-source-order table from src/node/utils/SkinColors.ts (last matching *-toolbar token wins). When no meta is present (non-colibris skin, server omits it) the helper is a no-op. Adds Playwright coverage for both paths under colorScheme: 'light' (manual toggle) and 'dark' (auto-switch on dark-OS clients — the case stffen reported). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fe9727b commit 4a6a71f

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

src/static/js/skin_variants.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@
44
const containers = ['editor', 'background', 'toolbar'];
55
const colors = ['super-light', 'light', 'dark', 'super-dark'];
66

7+
// Mirrors src/node/utils/SkinColors.ts: toolbar variants in CSS source order
8+
// from src/static/skins/colibris/src/pad-variants.css. The last matching token
9+
// wins, so iterate in source order.
10+
const TOOLBAR_COLORS_IN_CSS_ORDER: Array<[string, string]> = [
11+
['super-light-toolbar', '#ffffff'],
12+
['light-toolbar', '#f2f3f4'],
13+
['super-dark-toolbar', '#485365'],
14+
['dark-toolbar', '#576273'],
15+
];
16+
17+
// Keep <meta name="theme-color"> in sync with the toolbar the user actually
18+
// sees. The server emits a baseline derived from settings.skinVariants, but
19+
// pad.ts may flip the toolbar to super-dark on first paint (enableDarkMode
20+
// + prefers-color-scheme:dark + no localStorage white-mode override) and
21+
// the user can toggle via #options-darkmode. Without this, dark-mode users
22+
// keep the light meta and see a white address bar above a dark toolbar
23+
// (issue #7606 follow-up).
24+
const updateThemeColorMeta = (newClasses: string[]) => {
25+
const meta = document.querySelector('meta[name="theme-color"]');
26+
if (!meta) return;
27+
const tokens = new Set(newClasses.join(' ').split(/\s+/).filter(Boolean));
28+
let color = '#ffffff';
29+
for (const [variant, c] of TOOLBAR_COLORS_IN_CSS_ORDER) {
30+
if (tokens.has(variant)) color = c;
31+
}
32+
meta.setAttribute('content', color);
33+
};
34+
735
// add corresponding classes when config change
836
const updateSkinVariantsClasses = (newClasses) => {
937
const domsToUpdate = [
@@ -21,6 +49,8 @@ const updateSkinVariantsClasses = (newClasses) => {
2149
domsToUpdate.forEach((el) => { el.removeClass('full-width-editor'); });
2250

2351
domsToUpdate.forEach((el) => { el.addClass(newClasses.join(' ')); });
52+
53+
updateThemeColorMeta(newClasses);
2454
};
2555

2656

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {expect, test} from '@playwright/test';
2+
import {goToNewPad} from '../helper/padHelper';
3+
4+
const themeColor = (page: import('@playwright/test').Page) =>
5+
page.locator('meta[name="theme-color"]').getAttribute('content');
6+
7+
test.describe('light color scheme', () => {
8+
test.use({colorScheme: 'light'});
9+
10+
test('theme-color meta tracks the dark-mode toggle', async ({page}) => {
11+
await goToNewPad(page);
12+
// Server emits the light baseline derived from settings.skinVariants.
13+
expect(await themeColor(page)).toBe('#ffffff');
14+
15+
await page.locator('button[data-l10n-id="pad.toolbar.settings.title"]').click();
16+
await expect(page.locator('#theme-toggle-row')).toBeVisible();
17+
18+
// Colibris styles the native checkbox via a sibling label; click the label
19+
// so the toggle fires the real change event the production code listens on.
20+
await page.locator('label[for="options-darkmode"]').click();
21+
// pad.ts forces super-dark-toolbar (#485365) regardless of the configured
22+
// light skinVariants, so the meta must follow the client-applied class.
23+
await expect.poll(() => themeColor(page)).toBe('#485365');
24+
25+
await page.locator('label[for="options-darkmode"]').click();
26+
await expect.poll(() => themeColor(page)).toBe('#ffffff');
27+
});
28+
});
29+
30+
test.describe('dark color scheme', () => {
31+
test.use({colorScheme: 'dark'});
32+
33+
test('theme-color meta follows the auto dark-mode switch on dark-OS clients',
34+
async ({page}) => {
35+
await goToNewPad(page);
36+
// pad.ts auto-switches to super-dark-toolbar when enableDarkMode is on,
37+
// matchMedia(prefers-color-scheme:dark) matches, and no localStorage
38+
// white-mode override is set. The meta must follow the applied class —
39+
// this is the case stffen reported on issue #7606.
40+
await expect.poll(() => themeColor(page)).toBe('#485365');
41+
});
42+
});

0 commit comments

Comments
 (0)