style(transmittal): tokenize utility classes, drop !important dark overrides

transmittal/css/utilities.css hand-rolled a Tailwind-style utility
subset against hardcoded grayscale (#fff, #f9fafb, #d1d5db etc.)
tuned for light mode. Dark mode then needed a 17-line block of
!important rules in transmittal/css/base.css to swing each utility
class's background/text/border colors. That block was the worst
concentration of !important in the repo.

Tokenize the grayscale classes against shared CSS custom properties
(var(--bg), var(--bg-secondary), var(--text), var(--text-muted),
var(--border)) so the cascade picks up dark mode automatically:

  .bg-white, .bg-gray-50, .bg-gray-100  → var(--bg) / var(--bg-secondary)
  .text-gray-{400..700,900}             → var(--text-muted) / var(--text)
  .border, .border-{b,t}, .border-gray-* → var(--border)
  .hover:bg-gray-{50,100}               → var(--bg-hover)
  .focus:bg-white:focus                 → var(--bg)

Named-color text classes (.text-blue-600 / -green-600 / -red-600)
stay hardcoded — they encode link / success / danger semantics that
should not theme-shift.

The .table-filter-input dark-mode block also went — it was unused
(no element references it; .column-filter from shared is what gets
applied to the actual filter inputs).

!important count in transmittal's non-print CSS dropped from ~32 to
~12. The 6 transmittal Playwright specs still pass.

Verification needed: visually inspect transmittal in both light and
dark mode and flag any color regression. The token mapping is
mechanical but the live rendered output is the only proof.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-05-09 18:59:55 -05:00
parent 0c48a583ad
commit 538167b5c8
2 changed files with 32 additions and 64 deletions

View file

@ -107,49 +107,13 @@
border-bottom-color: #86efac; border-bottom-color: #86efac;
} }
/* Owner/Project names area and inline bg-white / bg-gray-50 utility classes */ /* Note: dark-mode overrides for .bg-white / .bg-gray-* / .text-gray-*
@media (prefers-color-scheme: dark) { / .border-gray-* and .header-names used to live here as a 17-line
:root:not([data-theme="light"]) .header-names { block of !important rules to fight hardcoded colors in
background-color: var(--bg-secondary) !important; transmittal/css/utilities.css. The utility classes were tokenized
border-color: var(--border) !important; (var(--bg), var(--bg-secondary), var(--text), var(--text-muted),
} var(--border)) so the cascade now does the right thing in both
:root:not([data-theme="light"]) .text-gray-700 { color: var(--text-muted) !important; } themes without per-class overrides. .table-filter-input is unused
:root:not([data-theme="light"]) .bg-white { background-color: var(--bg) !important; } (no element references it; .column-filter from shared is used
:root:not([data-theme="light"]) .bg-gray-50 { background-color: var(--bg-secondary) !important; } instead) and was likewise dropped. */
:root:not([data-theme="light"]) .bg-gray-100 { background-color: var(--bg-secondary) !important; }
:root:not([data-theme="light"]) .border-gray-100,
:root:not([data-theme="light"]) .border-gray-200,
:root:not([data-theme="light"]) .border-gray-300 { border-color: var(--border) !important; }
:root:not([data-theme="light"]) .text-gray-900 { color: var(--text) !important; }
}
[data-theme="dark"] .header-names {
background-color: var(--bg-secondary) !important;
border-color: var(--border) !important;
}
[data-theme="dark"] .text-gray-700 { color: var(--text-muted) !important; }
[data-theme="dark"] .bg-white { background-color: var(--bg) !important; }
[data-theme="dark"] .bg-gray-50 { background-color: var(--bg-secondary) !important; }
[data-theme="dark"] .bg-gray-100 { background-color: var(--bg-secondary) !important; }
[data-theme="dark"] .border-gray-100,
[data-theme="dark"] .border-gray-200,
[data-theme="dark"] .border-gray-300 { border-color: var(--border) !important; }
[data-theme="dark"] .text-gray-900 { color: var(--text) !important; }
/* Filter inputs in table column headers */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .table-filter-input {
background-color: var(--bg);
color: var(--text);
border-color: var(--border);
}
:root:not([data-theme="light"]) .table-header__caption { color: var(--text-muted); }
:root:not([data-theme="light"]) .focus\:bg-white:focus { background-color: var(--bg) !important; }
}
[data-theme="dark"] .table-filter-input {
background-color: var(--bg);
color: var(--text);
border-color: var(--border);
}
[data-theme="dark"] .table-header__caption { color: var(--text-muted); }
[data-theme="dark"] .focus\:bg-white:focus { background-color: var(--bg) !important; }
} }

View file

@ -16,11 +16,15 @@
.text-2xl { font-size: 1.5rem; line-height: 2rem; } .text-2xl { font-size: 1.5rem; line-height: 2rem; }
.text-\[12px\] { font-size: 12px; line-height: 1.4; } .text-\[12px\] { font-size: 12px; line-height: 1.4; }
.text-\[10px\] { font-size: 10px; line-height: 1.3; } .text-\[10px\] { font-size: 10px; line-height: 1.3; }
.text-gray-900 { color: #111827; } /* Gray-scale text classes are theme-encoding they map to shared
.text-gray-700 { color: #374151; } tokens so dark mode swaps automatically without per-class overrides.
.text-gray-600 { color: #4b5563; } The named-color text classes (.text-blue-600/-green-600/-red-600)
.text-gray-500 { color: #6b7280; } carry semantic meaning (link / success / danger) and stay hardcoded. */
.text-gray-400 { color: #9ca3af; } .text-gray-900 { color: var(--text); }
.text-gray-700 { color: var(--text-muted); }
.text-gray-600 { color: var(--text-muted); }
.text-gray-500 { color: var(--text-muted); }
.text-gray-400 { color: var(--text-muted); }
.text-blue-600 { color: #2563eb; } .text-blue-600 { color: #2563eb; }
.text-green-600 { color: #16a34a; } .text-green-600 { color: #16a34a; }
.text-red-600 { color: #dc2626; } .text-red-600 { color: #dc2626; }
@ -29,20 +33,20 @@
.leading-6 { line-height: 1.5rem; } .leading-6 { line-height: 1.5rem; }
.leading-snug { line-height: 1.375rem; } .leading-snug { line-height: 1.375rem; }
/* Backgrounds */ /* Backgrounds — gray-scale classes map to shared tokens. */
.bg-white { background-color: #ffffff; } .bg-white { background-color: var(--bg); }
.bg-transparent { background-color: transparent; } .bg-transparent { background-color: transparent; }
.bg-gray-50 { background-color: #f9fafb; } .bg-gray-50 { background-color: var(--bg-secondary); }
.bg-gray-100 { background-color: #f3f4f6; } .bg-gray-100 { background-color: var(--bg-secondary); }
/* Borders */ /* Borders — gray-scale border classes map to the shared token. */
.border { border: 1px solid #d1d5db; } .border { border: 1px solid var(--border); }
.border-0 { border: 0; } .border-0 { border: 0; }
.border-b { border-bottom: 1px solid #d1d5db; } .border-b { border-bottom: 1px solid var(--border); }
.border-t { border-top: 1px solid #d1d5db; } .border-t { border-top: 1px solid var(--border); }
.border-gray-300 { border-color: #d1d5db; } .border-gray-300 { border-color: var(--border); }
.border-gray-200 { border-color: #e5e7eb; } .border-gray-200 { border-color: var(--border); }
.border-gray-100 { border-color: #f3f4f6; } .border-gray-100 { border-color: var(--border); }
.rounded-none { border-radius: 0; } .rounded-none { border-radius: 0; }
.rounded-sm { border-radius: 0.125rem; } .rounded-sm { border-radius: 0.125rem; }
.rounded { border-radius: 0.25rem; } .rounded { border-radius: 0.25rem; }
@ -129,14 +133,14 @@
.border-red-500 { border-color: #ef4444 !important; } .border-red-500 { border-color: #ef4444 !important; }
/* Hover & focus states */ /* Hover & focus states */
.hover\:bg-gray-50:hover { background-color: #f9fafb; } .hover\:bg-gray-50:hover { background-color: var(--bg-hover); }
.hover\:bg-gray-100:hover { background-color: #f3f4f6; } .hover\:bg-gray-100:hover { background-color: var(--bg-hover); }
.hover\:underline:hover { text-decoration: underline; } .hover\:underline:hover { text-decoration: underline; }
.focus\:outline-none:focus { outline: none; } .focus\:outline-none:focus { outline: none; }
.focus\:border-blue-400:focus { border-color: #60a5fa; } .focus\:border-blue-400:focus { border-color: #60a5fa; }
.focus\:ring-1:focus { box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.35); } .focus\:ring-1:focus { box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.35); }
.focus\:ring-blue-400:focus { box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.45); } .focus\:ring-blue-400:focus { box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.45); }
.focus\:bg-white:focus { background-color: #ffffff; } .focus\:bg-white:focus { background-color: var(--bg); }
.disabled\:pointer-events-none:disabled { pointer-events: none; } .disabled\:pointer-events-none:disabled { pointer-events: none; }
/* Table helpers */ /* Table helpers */