fix(shopfloor): proper dark-mode via \$o-webclient-color-scheme branch
Dug deeper after the user reported shop-floor pages staying white in
dark mode. Traced through Odoo 19 source:
_dependencies/web_enterprise/static/src/
webclient/color_scheme/color_scheme_service.js <- reads cookie
scss/primary_variables.scss \$o-webclient-color-scheme: bright
scss/primary_variables.dark.scss \$o-webclient-color-scheme: dark
Odoo compiles TWO separate CSS bundles:
web.assets_backend -> compiled with \$...scheme: bright
web.assets_web_dark -> compiled with \$...scheme: dark
(the .dark.scss files are layered in front of the light ones)
Our shop-floor SCSS is in web.assets_backend, which means it gets
compiled into BOTH bundles. But the previous CSS-variable fallback
chain (var(--fp-page-bg, var(--bs-tertiary-bg, #hex))) baked the
SAME hex fallback into both bundles, so cards stayed white in dark.
Odoo's own code doesn't redefine --bs-* CSS custom properties at
runtime either — it just bakes the dark palette straight into the
dark bundle via SCSS \$-variables during compile.
Fix: _fp_shopfloor_tokens.scss now branches at compile time:
\$o-webclient-color-scheme: bright !default;
\$_fp-page-hex: #f3f4f6; // light defaults
\$_fp-card-hex: #ffffff;
...
@if \$o-webclient-color-scheme == dark {
\$_fp-page-hex: #1a1d21 !global;
\$_fp-card-hex: #22262d !global;
...
}
\$fp-page: var(--fp-page-bg, \$_fp-page-hex);
\$fp-card: var(--fp-card-bg, \$_fp-card-hex);
The CSS-custom-property fallback stays so deployments can still skin
via --fp-* without touching SCSS; the underlying hex changes between
bundles.
Verified via odoo-shell:
LIGHT bundle: .o_fp_plant_overview { background-color: var(...#f3f4f6) }
.o_fp_po_card { background-color: var(...#ffffff);
border: ... #d8dadd }
DARK bundle: .o_fp_plant_overview { background-color: var(...#1a1d21) }
.o_fp_po_card { background-color: var(...#22262d);
border: ... #343942 }
Two separate bundle URLs generated:
/web/assets/a593157/web.assets_backend.min.css
/web/assets/a9dba7d/web.assets_web_dark.min.css
=== CLAUDE.md ===
Replaced the previous (incorrect) .o_dark_mode override advice with
a proper "Branch on \$o-webclient-color-scheme at SCSS compile time"
section, including the bundle names and the verify-via-odoo-shell
snippet. Future redesigns now have a single, correct pattern to
follow.
Version bumped 19.0.4.0.0 -> 19.0.5.0.0 to force asset hash change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
37
CLAUDE.md
37
CLAUDE.md
@@ -29,16 +29,39 @@ For custom OWL dashboards / client actions use the same approach:
|
|||||||
$fp-border: var(--fp-border-color, #d8dadd);
|
$fp-border: var(--fp-border-color, #d8dadd);
|
||||||
```
|
```
|
||||||
- Reference those tokens everywhere (never `var(--bs-border-color)` directly)
|
- Reference those tokens everywhere (never `var(--bs-border-color)` directly)
|
||||||
- Override the custom properties for dark mode at the bottom of the tokens file:
|
|
||||||
```scss
|
|
||||||
:root[data-bs-theme="dark"], body.o_dark_mode, .o_dark_mode {
|
|
||||||
--fp-card-bg: #22262d;
|
|
||||||
--fp-border-color: #343942;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Three-layer contrast: **page** (grayest) → **container/column** (mid) → **card** (brightest). That's what makes cards pop.
|
- Three-layer contrast: **page** (grayest) → **container/column** (mid) → **card** (brightest). That's what makes cards pop.
|
||||||
- Reference implementation: `fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss`.
|
- Reference implementation: `fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss`.
|
||||||
|
|
||||||
|
## Dark Mode — Branch on `$o-webclient-color-scheme` at SCSS Compile Time
|
||||||
|
Odoo 19 does NOT flip dark mode via a runtime DOM class. It compiles TWO asset bundles:
|
||||||
|
- `web.assets_backend` — compiled with `$o-webclient-color-scheme: bright`
|
||||||
|
- `web.assets_web_dark` — compiled with `$o-webclient-color-scheme: dark` (dark variant primary variables loaded first)
|
||||||
|
|
||||||
|
Your SCSS file is compiled into BOTH bundles. To make the dark bundle have different colors, **branch at compile time** using the SCSS variable Odoo sets:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
$o-webclient-color-scheme: bright !default;
|
||||||
|
|
||||||
|
$_my-page-hex: #f3f4f6;
|
||||||
|
$_my-card-hex: #ffffff;
|
||||||
|
|
||||||
|
@if $o-webclient-color-scheme == dark {
|
||||||
|
$_my-page-hex: #1a1d21 !global;
|
||||||
|
$_my-card-hex: #22262d !global;
|
||||||
|
}
|
||||||
|
|
||||||
|
$my-page: var(--my-page-bg, $_my-page-hex);
|
||||||
|
$my-card: var(--my-card-bg, $_my-card-hex);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Do NOT use** `.o_dark_mode` class selectors, `[data-bs-theme="dark"]`, or `@media (prefers-color-scheme: dark)` — none of those fire reliably in Odoo 19. The user toggles dark mode via the user profile, which sets a `color_scheme` cookie and reloads the page; Odoo then serves the dark bundle. Your SCSS `@if` handles the rest at compile time.
|
||||||
|
|
||||||
|
Verify by inspecting the attachments — you should see two files with different URLs for the two bundles:
|
||||||
|
```python
|
||||||
|
env['ir.qweb']._get_asset_bundle('web.assets_backend').css() # light
|
||||||
|
env['ir.qweb']._get_asset_bundle('web.assets_web_dark').css() # dark
|
||||||
|
```
|
||||||
|
|
||||||
## Asset Bundle Cache Busting
|
## Asset Bundle Cache Busting
|
||||||
Odoo content-hashes the compiled bundle URL (`/web/assets/<hash>/...`). When CSS changes but the hash doesn't update, the browser serves the old bundle. Fixes in order of escalation:
|
Odoo content-hashes the compiled bundle URL (`/web/assets/<hash>/...`). When CSS changes but the hash doesn't update, the browser serves the old bundle. Fixes in order of escalation:
|
||||||
1. Bump the module `version` in `__manifest__.py`
|
1. Bump the module `version` in `__manifest__.py`
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Fusion Plating — Shop Floor',
|
'name': 'Fusion Plating — Shop Floor',
|
||||||
'version': '19.0.4.0.0',
|
'version': '19.0.5.0.0',
|
||||||
'category': 'Manufacturing/Plating',
|
'category': 'Manufacturing/Plating',
|
||||||
'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer, '
|
'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer, '
|
||||||
'first-piece inspection gates.',
|
'first-piece inspection gates.',
|
||||||
|
|||||||
@@ -31,40 +31,60 @@ $fp-radius-lg : 20px;
|
|||||||
$fp-radius-xl : 28px;
|
$fp-radius-xl : 28px;
|
||||||
$fp-radius-pill: 999px;
|
$fp-radius-pill: 999px;
|
||||||
|
|
||||||
// ---------- Surfaces — tied to Odoo's Bootstrap tokens -----------------------
|
// ---------- Surfaces — COMPILE-TIME branch on Odoo's dark scheme -------------
|
||||||
//
|
//
|
||||||
// Odoo 19's dark mode is NOT a runtime class toggle — it's a separate
|
// Odoo 19 compiles TWO asset bundles: web.assets_backend (light) and
|
||||||
// compiled CSS bundle where --bs-body-bg, --bs-card-bg, --bs-tertiary-bg,
|
// web.assets_web_dark (dark). The two bundles differ only in the value
|
||||||
// --bs-border-color are baked in with dark values. So if we chain our
|
// of the SCSS variable $o-webclient-color-scheme — `bright` for light,
|
||||||
// tokens through those Bootstrap variables, they auto-invert when Odoo
|
// `dark` for dark (defined in primary_variables.scss /
|
||||||
// swaps the bundle; no .o_dark_mode override needed.
|
// primary_variables.dark.scss in web_enterprise).
|
||||||
//
|
//
|
||||||
// Fallback order:
|
// Odoo does NOT redefine --bs-body-bg / --bs-card-bg as CSS custom
|
||||||
// 1. --fp-* (custom override, e.g. shop-branded skin)
|
// properties at runtime. It bakes the chosen palette into the bundle
|
||||||
// 2. --bs-* (Odoo/Bootstrap semantic token — inverts on dark)
|
// at compile time via Bootstrap SCSS variables. So our tokens must do
|
||||||
// 3. explicit hex (absolute fallback if vars are missing)
|
// the same: branch on $o-webclient-color-scheme at compile time and
|
||||||
$fp-page : var(--fp-page-bg,
|
// emit the right hex values into each bundle.
|
||||||
var(--bs-tertiary-bg, #f3f4f6));
|
|
||||||
$fp-card : var(--fp-card-bg,
|
|
||||||
var(--bs-card-bg,
|
|
||||||
var(--o-view-background-color, #ffffff)));
|
|
||||||
$fp-card-soft : var(--fp-card-soft-bg,
|
|
||||||
var(--bs-secondary-bg, #f1f3f5));
|
|
||||||
$fp-border : var(--fp-border-color,
|
|
||||||
var(--bs-border-color, #d8dadd));
|
|
||||||
$fp-border-strong : var(--fp-border-strong,
|
|
||||||
var(--bs-border-color-translucent, #b6babf));
|
|
||||||
|
|
||||||
// ---------- Text tiers — same chain pattern ----------------------------------
|
$o-webclient-color-scheme: bright !default;
|
||||||
$fp-ink : var(--fp-ink, var(--bs-body-color, #1f2937));
|
|
||||||
$fp-ink-soft : var(--fp-ink-soft, var(--bs-secondary-color, #4b5563));
|
|
||||||
$fp-ink-mute : var(--fp-ink-mute, var(--bs-tertiary-color, #6b7280));
|
|
||||||
$fp-ink-faint : var(--fp-ink-faint,
|
|
||||||
color-mix(in srgb, var(--bs-body-color, #000) 40%, transparent));
|
|
||||||
|
|
||||||
// Action colour — Odoo's primary. Explicit hex fallback to Odoo's
|
// Default (light / bright) palette
|
||||||
// default brand purple so .btn-primary doesn't render transparent.
|
$_fp-page-hex : #f3f4f6;
|
||||||
$fp-accent : var(--o-action, #714B67);
|
$_fp-card-hex : #ffffff;
|
||||||
|
$_fp-card-soft-hex : #f1f3f5;
|
||||||
|
$_fp-border-hex : #d8dadd;
|
||||||
|
$_fp-border-strong-hex : #b6babf;
|
||||||
|
$_fp-ink-hex : #1f2937;
|
||||||
|
$_fp-ink-soft-hex : #4b5563;
|
||||||
|
$_fp-ink-mute-hex : #6b7280;
|
||||||
|
$_fp-ink-faint-hex : #9ca3af;
|
||||||
|
|
||||||
|
// Dark palette — engaged when the dark bundle is compiled
|
||||||
|
@if $o-webclient-color-scheme == dark {
|
||||||
|
$_fp-page-hex : #1a1d21 !global;
|
||||||
|
$_fp-card-hex : #22262d !global;
|
||||||
|
$_fp-card-soft-hex : #1c2027 !global;
|
||||||
|
$_fp-border-hex : #343942 !global;
|
||||||
|
$_fp-border-strong-hex : #4a505a !global;
|
||||||
|
$_fp-ink-hex : #e5e7eb !global;
|
||||||
|
$_fp-ink-soft-hex : #c8ccd2 !global;
|
||||||
|
$_fp-ink-mute-hex : #8a909a !global;
|
||||||
|
$_fp-ink-faint-hex : #5a606b !global;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public tokens — CSS custom property fallback chain remains so a
|
||||||
|
// deployment can still override via --fp-* without touching SCSS.
|
||||||
|
$fp-page : var(--fp-page-bg, $_fp-page-hex);
|
||||||
|
$fp-card : var(--fp-card-bg, $_fp-card-hex);
|
||||||
|
$fp-card-soft : var(--fp-card-soft-bg, $_fp-card-soft-hex);
|
||||||
|
$fp-border : var(--fp-border-color, $_fp-border-hex);
|
||||||
|
$fp-border-strong : var(--fp-border-strong, $_fp-border-strong-hex);
|
||||||
|
$fp-ink : var(--fp-ink, $_fp-ink-hex);
|
||||||
|
$fp-ink-soft : var(--fp-ink-soft, $_fp-ink-soft-hex);
|
||||||
|
$fp-ink-mute : var(--fp-ink-mute, $_fp-ink-mute-hex);
|
||||||
|
$fp-ink-faint : var(--fp-ink-faint, $_fp-ink-faint-hex);
|
||||||
|
|
||||||
|
// Action colour — Odoo's primary. Same in both bundles (brand purple).
|
||||||
|
$fp-accent : var(--o-action, #714B67);
|
||||||
|
|
||||||
// ---------- Elevation — explicit rgba shadows --------------------------------
|
// ---------- Elevation — explicit rgba shadows --------------------------------
|
||||||
// Explicit rgba values (not color-mix) so they render identically across
|
// Explicit rgba values (not color-mix) so they render identically across
|
||||||
|
|||||||
Reference in New Issue
Block a user