fix: MPS-24 — systemic dark-mode token sweep (close the whole bug class)

After fixing the same dark-mode bug 4x one surface at a time, swept
it system-wide. Root pattern: var(--name, fallback) where --name is
NOT a token in tokens.css -> the light fallback applied in BOTH themes
-> dark broken. Offenders: --color-surface*, --color-border*,
--color-text*, --text-color, --surface* (none are tokens).

- Remapped all 53 occurrences in common.css to the real theme-aware
  tokens (--surface-base/-dim/-container, --border-default,
  --text-primary/-body/-muted), KEEPING each fallback literal
  (comma-boundary sed). Diff verified: exactly 53/53 var-name-only
  swaps, no fallback/structure change, line count unchanged.
- Light mode: identical where token==fallback (#fff, off-whites);
  minor canonical nudges where they differ (muted #888->#666, body
  #333->#515151, primary #111->#333, borders ->#e0e0e0) — the design
  system's intended values, the 'light looks better' direction.
- Dark mode fixed app-wide (wells, suggest cards, counts, checksum
  table, and every other surface using these vars).
- Excluded (not the bug): --shop-theme-*, --color-accent, --pico-*,
  --primary-color, the --dark-* family, theme-neutral font/size vars.
- CLAUDE.md: DARK-MODE TRAP rule + pre-commit grep gate. mps-24.md
  Phase 2.8r. 1151 passed (CSS-only).
This commit is contained in:
russell@unturf.com 2026-05-18 13:21:40 -04:00
parent b39efcad4e
commit 64bc435a00
No known key found for this signature in database
3 changed files with 79 additions and 53 deletions

View file

@ -290,6 +290,11 @@ Note: `align-items`, `justify-items`, `align-content`, `justify-content`, `place
**DESIGN TOKENS**: All new styles must consume tokens from `tokens.css` — never hardcode colors, spacing, radii, shadows, or font sizes. Use `var(--token-name)` or `var(--token-name, fallback)`. Our token scale uses a 4px spacing base and major third (1.250) type scale.
**DARK-MODE TRAP — only reference vars that are REAL tokens.** A `var(--name, fallback)` where `--name` is **not** defined in `tokens.css` silently uses the light `fallback` in BOTH themes → looks fine in light, broken (light card / invisible text) in dark. This bit us repeatedly (wells, suggest cards, checksum table). The offenders were ad-hoc names like `--color-surface*`, `--color-border*`, `--color-text*`, `--text-color`, `--surface*` — none are tokens. **Use the real theme-aware tokens**: surfaces → `--surface-base` / `--surface-dim` / `--surface-container`; borders → `--border-light` / `--border-default` / `--border-color`; text → `--text-primary` / `--text-body` / `--text-muted` (all carry `:root` + `[data-theme="dark"]` values). Pre-commit grep gate (must be empty):
```
grep -oE 'var\(\s*--(color-(surface|border|text)[a-z0-9-]*|surface(-[a-z]+)?|text-color)\s*,' make_post_sell/static/css/common.css
```
**STYLEGUIDE**: When creating new UI components (buttons, wells, alerts, layout patterns, etc.), add a live example to `/styleguide` (`make_post_sell/templates/styleguide.j2`). Our styleguide is our single source of truth for our component library. If it's not in our styleguide, it doesn't exist as a pattern.
**CSS MEDIA SIZING**: Never combine `width: 100%` with `max-height` on media elements (img, video). `width: 100%` forces our element to span our full container even when `max-height` constrains our rendered content, creating dead whitespace. Use `width: auto` + `max-width: 100%` + `max-height` instead — our element shrinks to match our actual content aspect ratio within both constraints.

View file

@ -363,6 +363,27 @@ Tests (`test_functional.py::TestProductTagsSpa`):
Deferred (occasional click, not the hot path): AJAX-ifying the
"Suggest categories" link — still a full navigation by design.
**Phase 2.8r — systemic dark-mode token sweep** (shipped 2026-05-18):
after fixing the same dark-mode bug 4× one-surface-at-a-time (wells,
suggest cards, counts, checksum table) the operator asked for a
systemic pass. Root pattern: CSS referenced `var(--name, fallback)`
where `--name` is **not** a token in `tokens.css` (`--color-surface*`,
`--color-border*`, `--color-text*`, `--text-color`, `--surface*`), so
the light `fallback` applied in BOTH themes → dark broken. Swept all
53 occurrences in `common.css` to the real theme-aware tokens
(`--surface-base/-dim/-container`, `--border-default`,
`--text-primary/-body/-muted`), **keeping each fallback literal**
(comma-boundary sed; verified diff is exactly 53/53 var-name-only
swaps, no fallback/structure change, line count unchanged). Light
mode now uses canonical token values: identical where token==fallback
(`#fff`, off-whites); minor design-consistent nudges where they
differ (muted `#888→#666`, body `#333→#515151`, primary `#111→#333`,
borders → `#e0e0e0`). Dark mode fixed app-wide. Excluded (not the
bug / runtime-defined): `--shop-theme-*`, `--color-accent`,
`--pico-*`, `--primary-color`, the `--dark-*` family (dark fallbacks,
dark-only rules), and theme-neutral font/size/radius vars. CLAUDE.md
gains a "DARK-MODE TRAP" rule + pre-commit grep gate.
**Phase 2.8q — click-to-copy hashes + styled checksum table** (shipped
2026-05-18): operator: make the checksum hashes click-to-copy (they
were unstyled, overflowing the column). New reusable

View file

@ -1482,18 +1482,18 @@ a.tag-chip {
display: inline-grid;
place-items: center;
padding: var(--space-1, 4px) var(--space-3, 12px);
border: 1px solid var(--color-border, #d1d5db);
border: 1px solid var(--border-default, #d1d5db);
border-radius: var(--radius-pill, 999px);
background: var(--color-surface, #fff);
color: var(--color-text, #111);
background: var(--surface-base, #fff);
color: var(--text-primary, #111);
font-size: var(--type-body-sm-size, 0.875rem);
text-decoration: none;
transition: background-color 150ms ease, border-color 150ms ease;
}
a.tag-chip:hover {
background: var(--color-surface-2, #f3f4f6);
border-color: var(--color-border-strong, #9ca3af);
background: var(--surface-container, #f3f4f6);
border-color: var(--border-default, #9ca3af);
text-decoration: none;
}
@ -1545,10 +1545,10 @@ li.tag-chip-removable {
gap: var(--space-2, 8px);
margin: 0 var(--space-2, 8px) var(--space-2, 8px) 0;
padding: var(--space-1, 4px) var(--space-2, 8px) var(--space-1, 4px) var(--space-3, 12px);
border: 1px solid var(--color-border, #d1d5db);
border: 1px solid var(--border-default, #d1d5db);
border-radius: var(--radius-pill, 999px);
background: var(--color-surface, #fff);
color: var(--color-text, #111);
background: var(--surface-base, #fff);
color: var(--text-primary, #111);
font-size: var(--type-body-sm-size, 0.875rem);
}
@ -1566,7 +1566,7 @@ button.tag-chip-removable-x {
border: none;
border-radius: var(--radius-pill, 999px);
background: transparent;
color: var(--color-text-muted, #6b7280);
color: var(--text-muted, #6b7280);
font-size: var(--type-body-size, 1rem);
line-height: 1;
cursor: pointer;
@ -1639,8 +1639,8 @@ form.serp-sort-form select {
padding: var(--space-1, 4px) var(--space-2, 8px);
border-radius: var(--radius-sm, 4px);
border: 1px solid var(--border-color, #dee2e6);
background: var(--surface-color, #fff);
color: var(--text-color, #222);
background: var(--surface-base, #fff);
color: var(--text-body, #222);
font-size: var(--type-body-sm-size, 0.875rem);
}
@media (max-width: 600px) {
@ -1719,7 +1719,7 @@ p.serp-list-price a {
}
p.serp-list-excerpt {
margin: 0;
color: var(--text-color, #333);
color: var(--text-body, #333);
font-size: var(--type-body-sm-size, 0.875rem);
line-height: 1.5;
/* No max-width the excerpt fills the row body so the layout uses
@ -1870,7 +1870,7 @@ li.tag-list-item {
align-items: center;
gap: var(--space-3, 12px);
padding: var(--space-2, 8px);
border: 1px solid var(--color-border, #e5e7eb);
border: 1px solid var(--border-default, #e5e7eb);
border-radius: var(--radius-sm, 4px);
}
li.tag-list-item.tag-list-drop-target {
@ -1995,9 +1995,9 @@ div.tag-flash {
div.tag-flash-toast {
padding: var(--space-2, 8px) var(--space-3, 12px);
border-radius: var(--radius-sm, 4px);
border: 1px solid var(--color-border, #d1d5db);
background: var(--color-surface-2, #f9fafb);
color: var(--color-text, #111);
border: 1px solid var(--border-default, #d1d5db);
background: var(--surface-container, #f9fafb);
color: var(--text-primary, #111);
transition: opacity 400ms ease;
}
@ -2090,7 +2090,7 @@ li.serp-rail-card {
padding: var(--space-2, 8px);
border: 1px solid var(--border-color, #e5e7eb);
border-radius: var(--radius-sm, 4px);
background: var(--surface, #fff);
background: var(--surface-base, #fff);
}
a.serp-rail-thumb-link {
display: block;
@ -2134,14 +2134,14 @@ span.serp-rail-card-price {
details.facet-details {
margin: var(--space-3, 12px) 0;
padding: var(--space-3, 12px);
background: var(--surface, #fff);
background: var(--surface-base, #fff);
border: 1px solid var(--border-color, #e5e7eb);
border-radius: var(--radius-md, 8px);
}
details.facet-details summary.facet-details-summary {
cursor: pointer;
font-weight: 600;
color: var(--text-color, #333);
color: var(--text-body, #333);
padding: var(--space-1, 4px) 0;
}
span.facet-details-active-mark {
@ -2182,7 +2182,7 @@ h2.facet-title {
margin: 0;
font-size: var(--type-body-size, 1rem);
font-weight: 600;
color: var(--text-color, #333);
color: var(--text-body, #333);
letter-spacing: 0.02em;
}
select.facet-select {
@ -2190,8 +2190,8 @@ select.facet-select {
padding: var(--space-2, 8px);
border: 1px solid var(--border-color, #d1d5db);
border-radius: var(--radius-sm, 4px);
background: var(--surface, #fff);
color: var(--text-color, #333);
background: var(--surface-base, #fff);
color: var(--text-body, #333);
}
div.facet-price-range {
display: grid;
@ -2211,8 +2211,8 @@ input.facet-price-input {
padding: var(--space-2, 8px);
border: 1px solid var(--border-color, #d1d5db);
border-radius: var(--radius-sm, 4px);
background: var(--surface, #fff);
color: var(--text-color, #333);
background: var(--surface-base, #fff);
color: var(--text-body, #333);
min-width: 0;
}
a.facet-clear-link {
@ -2240,7 +2240,7 @@ a.facet-tag {
gap: var(--space-2, 8px);
padding: var(--space-2, 8px) var(--space-3, 12px);
border-radius: var(--radius-sm, 4px);
color: var(--text-color, #333);
color: var(--text-body, #333);
text-decoration: none;
transition: background-color 150ms ease;
word-break: break-word;
@ -2318,7 +2318,7 @@ div.edit-page > section.well2 > form > h3:first-child {
/* Pricing-mode fieldset on edit page — improved spacing. */
div.edit-page fieldset {
border: 1px solid var(--color-border, #ddd);
border: 1px solid var(--border-default, #ddd);
border-radius: var(--radius-md, 8px);
padding: var(--space-3, 12px) var(--space-4, 16px);
margin: var(--space-3, 12px) 0;
@ -2327,7 +2327,7 @@ div.edit-page fieldset {
div.edit-page fieldset legend {
padding: 0 var(--space-2, 8px);
font-weight: 600;
color: var(--color-text-muted, #666);
color: var(--text-muted, #666);
}
div.edit-page fieldset label[style*="display:block"],
@ -2346,7 +2346,7 @@ div.edit-page > section.product-title-and-description {
Used in the upload/preview/thumbnail blocks of the edit page. */
.file-meta {
margin: var(--space-1, 4px) 0;
color: var(--color-text-muted, #666);
color: var(--text-muted, #666);
font-size: 0.9em;
}
@ -2356,7 +2356,7 @@ div.edit-page > section.product-title-and-description {
.file-meta-item + .file-meta-item::before {
content: " · ";
color: var(--color-text-muted, #aaa);
color: var(--text-muted, #aaa);
margin: 0 var(--space-1, 4px);
}
@ -2371,13 +2371,13 @@ div.edit-page > section.product-title-and-description {
font-size: 0.95rem;
font-weight: 600;
margin: 0 0 var(--space-2, 8px);
color: var(--color-text-primary, #333);
color: var(--text-primary, #333);
}
/* Form helper text — small tag below labels that doesn't break layout. */
.form-help {
display: block;
color: var(--color-text-muted, #666);
color: var(--text-muted, #666);
font-size: 0.85em;
margin: var(--space-1, 4px) 0 var(--space-2, 8px);
}
@ -2423,8 +2423,8 @@ div.edit-page > section.well2 > form > h3:first-child {
gap: var(--space-3, 12px);
margin-bottom: var(--space-3, 12px);
padding-bottom: var(--space-3, 12px);
border-bottom: 1px solid var(--color-border, #eee);
color: var(--color-text-primary, #222);
border-bottom: 1px solid var(--border-default, #eee);
color: var(--text-primary, #222);
}
.edit-card-icon {
@ -2452,7 +2452,7 @@ div.edit-page > section.well2 > form > h3:first-child {
.edit-permanent-link {
text-align: right;
margin: 0;
color: var(--color-text-muted, #666);
color: var(--text-muted, #666);
font-size: 0.85em;
}
@ -2467,7 +2467,7 @@ div.edit-page > section.well2 > form > h3:first-child {
border-radius: var(--radius-pill, 999px);
font-size: 0.85em;
font-weight: 600;
background: var(--color-text-muted, #888);
background: var(--text-muted, #888);
color: #fff;
}
@ -2485,7 +2485,7 @@ div.edit-page > section.well2 > form > h3:first-child {
}
.edit-status-pill-private {
background: var(--color-text-muted, #888);
background: var(--text-muted, #888);
}
.edit-status-pill-unlisted {
@ -2535,10 +2535,10 @@ div.edit-page button.mps-submit-primary:disabled,
bottom: var(--space-3, 12px);
margin-top: var(--space-5, 20px);
padding: var(--space-3, 12px) var(--space-4, 16px);
background: var(--surface, #fff);
background: var(--surface-base, #fff);
border-radius: var(--radius-md, 8px);
box-shadow: var(--elevation-3, 0 4px 12px rgba(0,0,0,0.07));
border: 1px solid var(--color-border, #eee);
border: 1px solid var(--border-default, #eee);
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
@ -2547,7 +2547,7 @@ div.edit-page button.mps-submit-primary:disabled,
}
.edit-save-bar-status {
color: var(--color-text-muted, #666);
color: var(--text-muted, #666);
font-size: 0.9em;
}
@ -2841,7 +2841,7 @@ section.upload-product-and-preview {
.upload-thumbnails-hint {
margin: 0;
color: var(--color-text-muted, #666);
color: var(--text-muted, #666);
text-align: right;
max-width: 320px;
}
@ -2864,7 +2864,7 @@ section.upload-product-and-preview {
gap: var(--space-2, 8px);
padding: var(--space-3, 12px);
background: var(--surface-dim, #f9f9fa);
border: 1px solid var(--color-border, #eee);
border: 1px solid var(--border-default, #eee);
border-radius: var(--radius-md, 8px);
min-width: 0;
}
@ -2874,7 +2874,7 @@ section.upload-product-and-preview {
max-width: 100%;
height: 140px;
object-fit: contain;
background: var(--surface, #fff);
background: var(--surface-base, #fff);
border-radius: var(--radius-sm, 4px);
}
@ -5611,14 +5611,14 @@ textarea {
padding: 10px 14px;
align-items: center;
text-align: left;
background: var(--surface-secondary, #f6f8fa);
background: var(--surface-dim, #f6f8fa);
border: 1px solid var(--border-color, #dee2e6);
border-radius: var(--radius-md, 8px);
margin-top: 8px;
}
[data-theme="dark"] .watch-countdown {
background: var(--surface-secondary, #161b22);
background: var(--surface-dim, #161b22);
border-color: var(--border-color, #7ab9ff);
}
@ -5884,8 +5884,8 @@ textarea {
padding: var(--space-1, 4px) var(--space-2, 8px);
border-radius: var(--radius-sm, 4px);
border: 1px solid var(--border-color, #dee2e6);
background: var(--surface-color, #fff);
color: var(--text-color, #222);
background: var(--surface-base, #fff);
color: var(--text-body, #222);
font-size: var(--font-size-sm, 0.875rem);
}
@ -6387,7 +6387,7 @@ html[data-color-filter="7"] {
padding: var(--space-2, 8px);
border: 1px solid var(--border-default, #ccc);
border-radius: var(--radius-sm, 4px);
background: var(--surface-default, #fff);
background: var(--surface-base, #fff);
color: var(--text-primary, #333);
}
@ -6443,7 +6443,7 @@ html[data-color-filter="7"] {
}
.auction-stat-label {
color: var(--color-text-muted, #888);
color: var(--text-muted, #888);
font-size: 0.85em;
}
@ -6466,10 +6466,10 @@ html[data-color-filter="7"] {
font-weight: bold;
text-align: center;
color: #fff;
background: var(--color-text-muted, #888);
background: var(--text-muted, #888);
}
.auction-state-0 { background: var(--color-text-muted, #888); } /* Draft */
.auction-state-0 { background: var(--text-muted, #888); } /* Draft */
.auction-state-1 { background: var(--color-info, #17a2b8); } /* Scheduled */
.auction-state-2 { background: var(--color-success, #28a745); } /* Active */
.auction-state-3 { background: var(--color-warning, #ffc107); color: #333; } /* Ended */
@ -6480,8 +6480,8 @@ html[data-color-filter="7"] {
.offer-state-1 { background: var(--color-success, #28a745); } /* Accepted */
.offer-state-2 { background: var(--color-primary, #5871ad); } /* Countered */
.offer-state-3 { background: var(--color-error, #dc3545); } /* Declined */
.offer-state-4 { background: var(--color-text-muted, #888); } /* Expired */
.offer-state-5 { background: var(--color-text-muted, #888); } /* Withdrawn */
.offer-state-4 { background: var(--text-muted, #888); } /* Expired */
.offer-state-5 { background: var(--text-muted, #888); } /* Withdrawn */
.offer-state-6 { background: var(--color-success, #28a745); } /* Paid */
.auction-flash {
@ -6511,7 +6511,7 @@ html[data-color-filter="7"] {
.offer-event {
padding: var(--space-2) 0;
border-bottom: 1px solid var(--color-border, #eee);
border-bottom: 1px solid var(--border-default, #eee);
}
.offer-event:last-child {