From 11f7791c5e8281dd23b1acbbdf685e0d4677a629 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 18 Apr 2026 19:22:17 -0400 Subject: [PATCH] fix(shopfloor): dark mode auto-inverts + Quick View button visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes + a memory entry in CLAUDE.md. === Dark mode === User: "when I change the theme the whole background does not turn dark like the other pages does". Digging through Odoo 19 source: /_dependencies/web_enterprise/static/src/scss/ bootstrap_overridden.dark.scss primary_variables.dark.scss secondary_variables.dark.scss Odoo doesn't flip dark mode via a runtime .o_dark_mode class on the DOM — it compiles a SEPARATE asset bundle where $o-webclient-color- scheme: dark is set, which redefines every --bs-* token with dark values. When the user toggles dark mode, Odoo swaps the whole CSS bundle. So my previous :root[data-bs-theme="dark"] { --fp-page-bg: #13161a; } block was DEAD CODE — nothing ever sets data-bs-theme on the root. Fixed: tokens now fall through to Bootstrap's --bs-* semantic tokens before hitting a hex default, so they auto-invert when Odoo swaps bundles. Three-level fallback chain: $fp-page : var(--fp-page-bg, var(--bs-tertiary-bg, #f3f4f6)); $fp-card : var(--fp-card-bg, var(--bs-card-bg, var(--o-view-background-color, #ffffff))); $fp-border : var(--fp-border-color, var(--bs-border-color, #d8dadd)); $fp-ink : var(--fp-ink, var(--bs-body-color, #1f2937)); Dead .o_dark_mode block removed. No runtime selector needed. === Quick View button === User: "Quick View button color is white with white button in light mode." Cause: Bootstrap's .btn-primary loads AFTER our custom CSS in the bundle and resets color: #fff, background: var(--bs-btn-bg) — which clobbered our $fp-accent / $fp-ink assignment because a later rule at the same specificity wins. Fix: split the primary button into its own rule with higher specificity (.o_fp_manager .o_fp_manager_head_actions .btn.btn-primary) and !important on the three key properties — so Bootstrap can't shout us down. Hover uses brightness(1.08) for a subtle darken without needing another color assignment. === CLAUDE.md additions === Added two new rules documenting the lessons so this isn't relearned: Rule 8 — Odoo 19 forbids @import in custom SCSS (silent warning, falls back to cached bundle). Register partials in the assets list in load order; SCSS variables cascade through the bundle. "Card Styling — Copy Odoo's Kanban Pattern" section explaining: - Don't rely on --bs-border-color directly for card surfaces - Chain through $fp-* → --fp-* → --bs-* → hex - 3-layer contrast rule (page → container → card) - Reference _fp_shopfloor_tokens.scss as canonical "Asset Bundle Cache Busting" section with 4-step escalation path for when CSS changes don't show up in browser. Verified: bundle regenerated to /web/assets/b48ab17/web.assets_backend.min.css (id 1945). Card rule compiled with full fallback chain visible. Primary button carries !important modifier for bg/border/color. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 31 +++++++ .../fusion_plating_shopfloor/__manifest__.py | 2 +- .../static/src/scss/_fp_shopfloor_tokens.scss | 84 ++++++++++--------- .../static/src/scss/manager_dashboard.scss | 22 +++-- 4 files changed, 92 insertions(+), 47 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5efc26c1..17f9f32b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,37 @@ 5. **res.config.settings**: Only boolean/integer/float/char/selection/many2one/datetime. NO Date fields. 6. **res.groups**: NO `users` field, NO `category_id` field. 7. **Search views**: NO `group expand="0"` syntax. +8. **SCSS imports**: `@import "./partial"` is FORBIDDEN in Odoo 19 custom SCSS. It prints a warning and silently falls back to the old cached bundle. Register every SCSS file (including `_partial.scss` tokens) as a separate entry in `web.assets_backend`. Put tokens first; Odoo concatenates bundle files so SCSS variables/mixins from the first file are visible to every later file. + +## Card Styling — Copy Odoo's Kanban Pattern +Don't rely on `var(--bs-border-color)` or `var(--bs-body-bg)` for card surfaces — they drift between themes/addons and often render **invisible**. Odoo's own kanban (`.o_kanban_record`) uses **explicit hex** values: +```css +background-color: white; +border: 1px solid #d8dadd; +``` +For custom OWL dashboards / client actions use the same approach: +- Define a `_tokens.scss` partial with explicit hex values wrapped in a CSS custom property: + ```scss + $fp-card: var(--fp-card-bg, #ffffff); + $fp-border: var(--fp-border-color, #d8dadd); + ``` +- 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. +- Reference implementation: `fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss`. + +## Asset Bundle Cache Busting +Odoo content-hashes the compiled bundle URL (`/web/assets//...`). 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` +2. `DELETE FROM ir_attachment WHERE url LIKE '/web/assets/%';` then restart odoo +3. Call `env['ir.qweb']._get_asset_bundle('web.assets_backend').css()` in odoo-shell to force regeneration +4. Hard-refresh browser with cache clear (DevTools → right-click refresh → *Empty Cache and Hard Reload*); on mobile clear website data ## Naming - New fields: `x_fc_*` prefix diff --git a/fusion_plating/fusion_plating_shopfloor/__manifest__.py b/fusion_plating/fusion_plating_shopfloor/__manifest__.py index 240b8f05..b007ec94 100644 --- a/fusion_plating/fusion_plating_shopfloor/__manifest__.py +++ b/fusion_plating/fusion_plating_shopfloor/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Shop Floor', - 'version': '19.0.3.0.0', + 'version': '19.0.4.0.0', 'category': 'Manufacturing/Plating', 'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer, ' 'first-piece inspection gates.', diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss index ac3709bf..d7899f58 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss @@ -31,26 +31,40 @@ $fp-radius-lg : 20px; $fp-radius-xl : 28px; $fp-radius-pill: 999px; -// ---------- Surfaces — explicit values, matching Odoo kanban conventions ----- -// Odoo's own kanban uses hex values (not CSS vars) because --bs-border-color -// and --bs-body-bg drift between themes and addons. We follow the same -// approach with clear, strong contrast: tinted page, tinted column, SOLID -// WHITE card with a VISIBLE border. +// ---------- Surfaces — tied to Odoo's Bootstrap tokens ----------------------- // -// Dark mode: overrides are applied at the bottom of each file via -// :root[data-bs-theme="dark"], body.o_dark_mode, or @media dark-pref. -$fp-page : var(--fp-page-bg, #f3f4f6); -$fp-column : var(--fp-column-bg, #e9ebef); -$fp-card : var(--fp-card-bg, #ffffff); -$fp-card-soft : var(--fp-card-soft-bg, #f8fafc); -$fp-border : var(--fp-border-color, #d8dadd); -$fp-border-strong : var(--fp-border-strong, #b6babf); +// Odoo 19's dark mode is NOT a runtime class toggle — it's a separate +// compiled CSS bundle where --bs-body-bg, --bs-card-bg, --bs-tertiary-bg, +// --bs-border-color are baked in with dark values. So if we chain our +// tokens through those Bootstrap variables, they auto-invert when Odoo +// swaps the bundle; no .o_dark_mode override needed. +// +// Fallback order: +// 1. --fp-* (custom override, e.g. shop-branded skin) +// 2. --bs-* (Odoo/Bootstrap semantic token — inverts on dark) +// 3. explicit hex (absolute fallback if vars are missing) +$fp-page : var(--fp-page-bg, + 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 ------------------------------------------------------- -$fp-ink : var(--fp-ink, #1f2937); -$fp-ink-soft : var(--fp-ink-soft, #4b5563); -$fp-ink-mute : var(--fp-ink-mute, #6b7280); -$fp-ink-faint : var(--fp-ink-faint, #9ca3af); +// ---------- Text tiers — same chain pattern ---------------------------------- +$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 brand purple so .btn-primary doesn't render transparent. +$fp-accent : var(--o-action, #714B67); // ---------- Elevation — explicit rgba shadows -------------------------------- // Explicit rgba values (not color-mix) so they render identically across @@ -65,12 +79,12 @@ $fp-elev-3 : 0 4px 8px rgba(0, 0, 0, 0.10), $fp-elev-hover : 0 6px 12px rgba(0, 0, 0, 0.12), 0 18px 36px rgba(0, 0, 0, 0.16); -// ---------- Semantic colour helpers (NOT gradients) -------------------------- -$fp-accent : var(--o-action); // the one action colour -$fp-ok : var(--bs-success); -$fp-warn : var(--bs-warning); -$fp-bad : var(--bs-danger); -$fp-info : var(--bs-info); +// ---------- Semantic colour helpers ------------------------------------------ +// (Note: $fp-accent defined earlier with its fallback — not redefined here) +$fp-ok : var(--bs-success, #28a745); +$fp-warn : var(--bs-warning, #ffc107); +$fp-bad : var(--bs-danger, #dc3545); +$fp-info : var(--bs-info, #17a2b8); // Softened backgrounds for status pills / banners @function fp-wash($color-var, $strength: 12%) { @@ -139,21 +153,9 @@ $fp-touch-min : 48px; // larger than Apple's 44px minimum — shop floor // ============================================================================= -// Dark-mode palette overrides -// Odoo 19 flips dark mode via [data-bs-theme="dark"] on the root. -// We provide a matching-level body selector for compatibility. +// Dark mode +// No class-based override needed — Odoo 19 serves a separate compiled bundle +// for dark mode, and all --bs-* tokens are redefined inside it. Because our +// $fp-* tokens fall through to --bs-* (see the surface definitions above), +// dark mode Just Works. // ============================================================================= -:root[data-bs-theme="dark"], -body.o_dark_mode, -.o_dark_mode { - --fp-page-bg : #13161a; - --fp-column-bg : #1a1e24; - --fp-card-bg : #22262d; - --fp-card-soft-bg : #1c2027; - --fp-border-color : #343942; - --fp-border-strong : #4a505a; - --fp-ink : #e5e7eb; - --fp-ink-soft : #c8ccd2; - --fp-ink-mute : #8a909a; - --fp-ink-faint : #5a606b; -} diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss index febc6b52..62017c2a 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss @@ -79,12 +79,13 @@ .o_fp_manager_head_actions { display: flex; gap: $fp-space-2; + // Secondary (default) button — plain card with border .btn { min-height: $fp-touch-min; padding: 0 $fp-space-4; border-radius: $fp-radius-md; font-weight: $fp-weight-semibold; - border: 1px solid #{$fp-border}; + border: 1px solid $fp-border; background-color: $fp-card; color: $fp-ink; box-shadow: $fp-elev-1; @@ -98,11 +99,22 @@ } } &:active { transform: scale(0.97); } + } - &.btn-primary { - background-color: $fp-accent; - border-color: $fp-accent; - color: var(--o-we-text-on-action, #fff); + // Primary — filled with the accent, white text. Force specificity + // high enough to beat Bootstrap's .btn-primary which loads later. + .btn.btn-primary, + .btn.btn-primary:focus, + .btn.btn-primary:active { + background-color: $fp-accent !important; + border-color: $fp-accent !important; + color: #ffffff !important; + + @include fp-hover-only { + &:hover { + filter: brightness(1.08); + border-color: $fp-accent !important; + } } } @media (max-width: 600px) {