fix(shopfloor): dark mode auto-inverts + Quick View button visible
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) <noreply@anthropic.com>
This commit is contained in:
31
CLAUDE.md
31
CLAUDE.md
@@ -14,6 +14,37 @@
|
|||||||
5. **res.config.settings**: Only boolean/integer/float/char/selection/many2one/datetime. NO Date fields.
|
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.
|
6. **res.groups**: NO `users` field, NO `category_id` field.
|
||||||
7. **Search views**: NO `group expand="0"` syntax.
|
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/<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`
|
||||||
|
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
|
## Naming
|
||||||
- New fields: `x_fc_*` prefix
|
- New fields: `x_fc_*` prefix
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Fusion Plating — Shop Floor',
|
'name': 'Fusion Plating — Shop Floor',
|
||||||
'version': '19.0.3.0.0',
|
'version': '19.0.4.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,26 +31,40 @@ $fp-radius-lg : 20px;
|
|||||||
$fp-radius-xl : 28px;
|
$fp-radius-xl : 28px;
|
||||||
$fp-radius-pill: 999px;
|
$fp-radius-pill: 999px;
|
||||||
|
|
||||||
// ---------- Surfaces — explicit values, matching Odoo kanban conventions -----
|
// ---------- Surfaces — tied to Odoo's Bootstrap tokens -----------------------
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
// Dark mode: overrides are applied at the bottom of each file via
|
// Odoo 19's dark mode is NOT a runtime class toggle — it's a separate
|
||||||
// :root[data-bs-theme="dark"], body.o_dark_mode, or @media dark-pref.
|
// compiled CSS bundle where --bs-body-bg, --bs-card-bg, --bs-tertiary-bg,
|
||||||
$fp-page : var(--fp-page-bg, #f3f4f6);
|
// --bs-border-color are baked in with dark values. So if we chain our
|
||||||
$fp-column : var(--fp-column-bg, #e9ebef);
|
// tokens through those Bootstrap variables, they auto-invert when Odoo
|
||||||
$fp-card : var(--fp-card-bg, #ffffff);
|
// swaps the bundle; no .o_dark_mode override needed.
|
||||||
$fp-card-soft : var(--fp-card-soft-bg, #f8fafc);
|
//
|
||||||
$fp-border : var(--fp-border-color, #d8dadd);
|
// Fallback order:
|
||||||
$fp-border-strong : var(--fp-border-strong, #b6babf);
|
// 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 -------------------------------------------------------
|
// ---------- Text tiers — same chain pattern ----------------------------------
|
||||||
$fp-ink : var(--fp-ink, #1f2937);
|
$fp-ink : var(--fp-ink, var(--bs-body-color, #1f2937));
|
||||||
$fp-ink-soft : var(--fp-ink-soft, #4b5563);
|
$fp-ink-soft : var(--fp-ink-soft, var(--bs-secondary-color, #4b5563));
|
||||||
$fp-ink-mute : var(--fp-ink-mute, #6b7280);
|
$fp-ink-mute : var(--fp-ink-mute, var(--bs-tertiary-color, #6b7280));
|
||||||
$fp-ink-faint : var(--fp-ink-faint, #9ca3af);
|
$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 --------------------------------
|
// ---------- 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
|
||||||
@@ -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),
|
$fp-elev-hover : 0 6px 12px rgba(0, 0, 0, 0.12),
|
||||||
0 18px 36px rgba(0, 0, 0, 0.16);
|
0 18px 36px rgba(0, 0, 0, 0.16);
|
||||||
|
|
||||||
// ---------- Semantic colour helpers (NOT gradients) --------------------------
|
// ---------- Semantic colour helpers ------------------------------------------
|
||||||
$fp-accent : var(--o-action); // the one action colour
|
// (Note: $fp-accent defined earlier with its fallback — not redefined here)
|
||||||
$fp-ok : var(--bs-success);
|
$fp-ok : var(--bs-success, #28a745);
|
||||||
$fp-warn : var(--bs-warning);
|
$fp-warn : var(--bs-warning, #ffc107);
|
||||||
$fp-bad : var(--bs-danger);
|
$fp-bad : var(--bs-danger, #dc3545);
|
||||||
$fp-info : var(--bs-info);
|
$fp-info : var(--bs-info, #17a2b8);
|
||||||
|
|
||||||
// Softened backgrounds for status pills / banners
|
// Softened backgrounds for status pills / banners
|
||||||
@function fp-wash($color-var, $strength: 12%) {
|
@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
|
// Dark mode
|
||||||
// Odoo 19 flips dark mode via [data-bs-theme="dark"] on the root.
|
// No class-based override needed — Odoo 19 serves a separate compiled bundle
|
||||||
// We provide a matching-level body selector for compatibility.
|
// 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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -79,12 +79,13 @@
|
|||||||
.o_fp_manager_head_actions {
|
.o_fp_manager_head_actions {
|
||||||
display: flex; gap: $fp-space-2;
|
display: flex; gap: $fp-space-2;
|
||||||
|
|
||||||
|
// Secondary (default) button — plain card with border
|
||||||
.btn {
|
.btn {
|
||||||
min-height: $fp-touch-min;
|
min-height: $fp-touch-min;
|
||||||
padding: 0 $fp-space-4;
|
padding: 0 $fp-space-4;
|
||||||
border-radius: $fp-radius-md;
|
border-radius: $fp-radius-md;
|
||||||
font-weight: $fp-weight-semibold;
|
font-weight: $fp-weight-semibold;
|
||||||
border: 1px solid #{$fp-border};
|
border: 1px solid $fp-border;
|
||||||
background-color: $fp-card;
|
background-color: $fp-card;
|
||||||
color: $fp-ink;
|
color: $fp-ink;
|
||||||
box-shadow: $fp-elev-1;
|
box-shadow: $fp-elev-1;
|
||||||
@@ -98,11 +99,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:active { transform: scale(0.97); }
|
&:active { transform: scale(0.97); }
|
||||||
|
}
|
||||||
|
|
||||||
&.btn-primary {
|
// Primary — filled with the accent, white text. Force specificity
|
||||||
background-color: $fp-accent;
|
// high enough to beat Bootstrap's .btn-primary which loads later.
|
||||||
border-color: $fp-accent;
|
.btn.btn-primary,
|
||||||
color: var(--o-we-text-on-action, #fff);
|
.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) {
|
@media (max-width: 600px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user