fix(web): overhaul dark-mode contrast in dashboard tables#1436
Merged
Conversation
Dark mode read as "pretty terrible": the Global Runs table header text was dark-gray on dark-gray (nearly invisible), secondary cells were dim, the "Unlinked" pill was low-contrast, and destructive action buttons rendered red-text-on-red-background. Root cause was NOT missing `dark:` variants (a grep found zero) — it was token-choice in 5 raw <table> components plus two bad dark token values. Tokens (web/src/index.css): - Lift dark --color-muted-foreground 0.708 → 0.75 for legible secondary text. - Fix --color-destructive-foreground in both themes (was a saturated red on the red destructive bg → red-on-red) to near-white oklch(0.985). This un-breaks the Cancel/Delete buttons that use `bg-destructive text-destructive-foreground`. - Define --radius (referenced by sonner.tsx but never declared). Migrate the 5 raw tables to the shadcn Table primitive (runs-table, work-item-runs-table, project-work-table, organizations-table, webhooklogs-table). Its TableHead uses text-foreground instead of the dim text-muted-foreground on bg-muted/50 — the invisible-header bug — and prevents any new raw table from reintroducing it. Behavior preserved: clickable rows, responsive hidden md:table-cell columns, alignment, tabular-nums, links, and title wrapping (whitespace-normal where the primitive's default nowrap differs). Badges: "Unlinked" pill → <Badge variant="outline">; status-badge unknown fallback brightened to dark:bg-gray-700 dark:text-gray-100. Verified: tsc -b, biome, vite build, and 562 web unit tests all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RyBTx5JozjbpUko5SRyz4X
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
nhopeatall
reviewed
Jun 23, 2026
nhopeatall
left a comment
Collaborator
There was a problem hiding this comment.
Summary
Well-scoped, correct dark-mode contrast fix. I traced the risky parts of the migration and they hold up; one gap worth a follow-up — a 6th raw table with the identical invisible-header pattern was missed.
Verified (no issues)
- Dropping the outer
overflow-x-autois safe — theTableprimitive wraps its<table>in<div className="relative w-full overflow-x-auto">(web/src/components/ui/table.tsx:9), so horizontal scroll on narrow viewports is preserved for the wide tables (e.g.runs-table's 11 columns). whitespace-normaloverrides land correctly —cn()istwMerge(clsx(...))(web/src/lib/utils.ts), so the explicitwhitespace-normalon title / empty-state cells properly beats the primitive's defaultwhitespace-nowrap.- The header fix is genuine — the primitive's
TableHeadusestext-foreground(nottext-muted-foreground), and[&_tr:last-child]:border-0on the primitiveTableBodypreserveswork-item-runs-table's oldlast:border-0behavior. - Token changes check out —
--color-destructive-foregroundwas=== --color-destructivein both themes (red-on-red); the token is only ever consumed astext-destructive-foregroundonbg-destructive(4 call sites, no bg/border usage), so near-white is correct for both themes.--radiusis genuinely referenced bysonner.tsx:30and was previously undefined.runs-tableempty-statecolSpan(10 / 11) matches the column count.
Should Fix
web/src/components/llm-calls/llm-call-list.tsx:211-226— a 6th raw<table>(the LLM-calls table on the run-detail page/runs/$runId) still has the exact pattern this PR is eliminating:text-muted-foreground<th>s inside<tr className="... bg-muted/50">. It wasn't migrated, so its header stays dim-gray-on-dark-gray in dark mode while the 5 migrated tables now rendertext-foregroundheaders — leaving the run-detail page exhibiting the very bug the PR fixes elsewhere, plus a visible header-contrast inconsistency between tables. The dark--muted-foregroundlift (0.708 → 0.75) only marginally helps it. Recommend migrating it to theTableprimitive too (or at minimum giving its<th>stext-foreground) so the fix — and the PR's "prevents any new raw table from reintroducing it" claim — is actually complete.
Non-blocking: the diff itself is correct and a clear net improvement.
🕵️ claude-code · claude-opus-4-8 · run details
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Dark mode reads as "pretty terrible": the Global Runs table header text is dark-gray on dark-gray (nearly invisible), secondary cells are dim, the "Unlinked" pill is low-contrast, and destructive action buttons render red-text-on-red-background.
Root cause is not missing
dark:variants (an audit grep found zero) — it's token-choice in 5 raw<table>components plus two bad dark token values.Changes
Tokens —
web/src/index.css--color-muted-foreground0.708 → 0.75(legible secondary text).--color-destructive-foreground(both themes): was a saturated red on the red destructive bg (red-on-red) → near-whiteoklch(0.985). Un-breaks the Cancel/Delete buttons (bg-destructive text-destructive-foreground).--radius(referenced bysonner.tsx, never declared).Migrate 5 raw tables → shadcn
Tableprimitive (runs-table,work-item-runs-table,project-work-table,organizations-table,webhooklogs-table). The primitive'sTableHeadusestext-foregroundinstead of the dimtext-muted-foregroundonbg-muted/50— the invisible-header bug — and prevents any new raw table from reintroducing it. Behavior preserved: clickable rows, responsivehidden md:table-cellcolumns, alignment,tabular-nums, links, and title wrapping (whitespace-normalwhere the primitive's defaultnowrapdiffers).Badges: "Unlinked" pill →
<Badge variant="outline">; status-badge unknown fallback brightened todark:bg-gray-700 dark:text-gray-100.Verification
tsc -b✅ ·biome check✅ ·vite build✅ · 562 web unit tests ✅ · full pre-push suite ✅. Visual confirmation to follow on the deployed dashboard (dark-mode Global Runs).🤖 Generated with Claude Code