Repository navigation
grid: a rowColor row in dark mode keeps its light-mode fill (bg-*-100) under the dark theme's light text, so the row is unreadable; the tint also skips the frozen columns #12375
Description
Filing gate ①: a user-visible defect, measured in a browser.
reach: the console, measured by the maintainer in a dark-theme browser on a local HotCRM console (/_console/apps/app.objectstack.hotcrm/crm_case, the Cases list, All Cases view): every Critical row is a pale pink band whose Subject and Account text is close to invisible.
Filed by the repo:hotcrm seat (session_01P8mZuzuyZAHSrgQKmv6ASv) on the maintainer's direct question in that session's chat. ⛔ Not a claim.
Reader: objectui triage, then the lane that owns packages/plugin-grid and packages/components (renderers/complex/data-table.tsx).
Dedupe (MCP search_issues, objectui, open + closed): "grid rowColor row background tint unreadable in dark mode" → 1 hit; "COLOR_TO_CLASS bg-red-100 dark variant pinned frozen column row color" → 1 hit. Both hits are objectui#758 (closed, row colour across view types); neither covers the dark theme or the frozen columns.
What happens
In the screenshot, with the dark theme on:
- The Critical rows (
rowColor: { field: 'priority', colors: { critical: 'red', … } }) render a light pink fill. The cell text keeps the dark theme's light foreground, so Subject (Demo case 00016 — critical feature_requ…) and Account (ACC-000005 - Wayne E…) are light text on a light fill and close to unreadable. The badges stay readable because they carry their own dark-mode pair. - The fill stops at the frozen columns. The checkbox,
#,Case Number(pinned: 'left') and the right-pinnedActionscolumn keep the page background, so every tinted row has dark gaps at both ends. The gaps come from the cell markup, so they show in the light theme too.
Producer (read on objectui origin/main df05784)
packages/plugin-grid/src/useRowColor.ts,COLOR_TO_CLASS: each colour name maps to one light-only class (red: 'bg-red-100',green: 'bg-green-100', … 23 entries) with nodark:partner.ObjectGrid.tsx:5897hands that class to the table asrowClassName.packages/components/src/renderers/complex/data-table.tsx(around line 3124): the row iscn("bg-background …", …, rowClassName(row)), so the authored class replacesbg-backgroundin both themes. The row sets no foreground, so the text is the theme'sforeground, which is light in the dark theme.- The same file's frozen cells paint their own
bg-background(sticky left-0 z-10 bg-backgroundat about lines 3179 and 3193, andisFrozen && 'sticky z-10 bg-background'at about line 3300). That is the hole in the tint. The right-pinned actions column does the same throughrightPinnedClasses(packages/plugin-grid/src/ObjectGrid.tsx:4826,sticky right-0 z-10 bg-background border-l border-border). - The workspace already has the dark-safe pair for this vocabulary.
BADGE_COLOR_MAPinpackages/fields/src/index.tsx(around line 1428) usesbg-red-50 … dark:bg-red-950/40. The data table's own unsaved-row fill usesbg-amber-50 dark:bg-amber-950/20, and its error fill usesbg-destructive/10 dark:bg-destructive/15.
Fix direction (for the implementer to measure, ⛔ not a ruling)
- Make every
COLOR_TO_CLASSvalue a complete light and dark literal pair on the same surface shades the badge palette uses, for examplered: 'bg-red-50 dark:bg-red-950/40'. Write each class out in full in the source: Tailwind v4 builds only from scanned source text, aspackages/plugin-detail/src/headerColor.tsdocuments (objectui#6178). - Keep the theme foreground. A tint is a surface and must not need its own text colour.
- Carry the tint into the frozen and right-pinned cells. Those cells sit over scrolled content, so their fill has to stay opaque in both themes. A translucent dark tint alone would let the content scrolling under them show through.
- A value an author writes as a
bg-*class still passes through verbatim. Whether it suits the dark theme is that author's choice, and therowColordocs could say so in one line.
Acceptance
- In both themes, the text and badges on a
rowColorrow are as readable as on an untinted row. Body text meets WCAG AA (4.5:1) against the tint. - The tint covers the whole row: checkbox, row number, frozen columns and the right-pinned actions column included.
- Every one of the 23 colour names resolves to a light and dark pair.
HotCRM declares rowColor on nine list views (Cases ×2, Accounts, Events, Event Attendees, Leads, Opportunities, Tasks, Knowledge Articles), always with colour names: never a hex value or a class. So the fix belongs entirely in the renderer, and HotCRM will not restyle around it (HotCRM AGENTS.md §4).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade ·
bug·priority:p2·domain:ui·area:records·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T14:52ZRead fresh.
priority:p2: a measured readability defect. In the dark theme arowColorrow keeps its lightbg-*-100fill under light text, so the row's content is close to unreadable. The tint also skips the pinned columns. Every grid withrowColoris affected under the dark theme; nothing is lost or written wrong.domain:ui(packages/plugin-gridandpackages/componentsrenderers/complex/data-table.tsx).objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 1
Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12375-rowcolor-dark-pinnedWorktree:
objectui-issue-12375Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/plugin-grid/src/useRowColor.ts,packages/plugin-grid/src/ObjectGrid.tsx(the right-pinned actions cell classes and therowClassNamehand-off only),packages/components/src/renderers/complex/data-table.tsx(the row and frozen-cell classes), their tests, therowColordocs line if one exists, and.changeset/entries (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate)Clause-②: no
Responsibility:
objectui's useRowColor COLOR_TO_CLASS (light-only classes) and data-table's frozen cells painting bg-background | none: no platform path styles the row | every grid with rowColor under the dark theme, and every tinted row with a pinned column in both themes; measured by the maintainer on HotCRM CasesThread-read: 6171215327
Serial constraints cleared: objectui#12376 (PR #12392,
packages/plugin-list/src/ListView.tsx) is disjoint from this surface. objectui#12393 (ObjectGrid.tsxgetSelectFields) is filed, not queued or claimed; this claim touches only the pinned-cell classes in that file. objectui#12326 is inpackages/app-shell.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{ "issue": 12375, "status": "done", "branch": "claude/issue-12375-rowcolor-dark-pinned", "pr": "https://github.com/objectstack-ai/objectui/pull/12394", "session": "session_01XVp19E1MYUTFM3dnkwz8gB (the parent's id; this run is a subagent)", "premise_still_valid": true, "summary": "The premise held on origin/main 96fcf305a. COLOR_TO_CLASS had 23 light-only bg-*-100 entries; the data-table row was cn(bg-background ..., rowClassName); the checkbox, row-number and frozen cells painted bg-background, and so did ObjectGrid's rightPinnedClasses. Each colour name now resolves to one surface class, bg-[color:var(--os-row-tint)], plus a light and a dark value of --os-row-tint, all complete literals. Light keeps the -100 shade. Dark uses the badge palette's -950 at 40%, or -800 at 50% for the six neutral names. Every row defaults the variable to transparent and resets it under hover and selection. Every sticky body cell (checkbox, row number, frozen columns, the right-pinned cells from ObjectGrid) keeps its opaque bg-background and layers the tint over it as a background image, so it matches the row and stays opaque. An authored bg-* value passes through verbatim. No refusal, prop or export was added.", "tests": "At 8fc72e270, under os-verify-lock: (1) closure build of the plugin-grid dependencies: BUILD_EXIT=0. (2) type-check for components and plugin-grid, which includes tsconfig.test.json: exit 0 and exit 0. (3) full vitest run of packages/plugin-grid/ and packages/components/: 598 files passed, 1 skipped; 5899 tests passed, 24 skipped; VERDICT command-exit 0. (4) Targeted run: rowColorTint-12375 74/74. Earlier, the 7-file targeted set had one red control; it was fixed, then 125 of 126 green plus the rerun. Ablation via objectstack scripts/ablation-replace.mjs, src-aliased in vitest so no dist is involved, with mutation and restore proven by blob hash and git diff HEAD: removing STICKY_ROW_TINT from the frozen data cell fails 2 tests across both new files; deleting red's dark half fails 'red resolves to a light value, a dark value, and the surface that paints them'; tree diff after = 0 lines. One-off Chromium 1194 measurement with real Tailwind 4.3.3 output: lowest foreground contrast on a tinted row in dark is 1.03 before and 16.80 after; light is 16.24 both. Sticky cell equals row within 1/255 per channel on all 23 names in both themes, and nothing shows through from beneath. Gates: 15 pre-tsc battery checks plus new-line-citations exit 0; spec-floors exit 1 (already red on main, objectui#12334); check-changeset-fixed and check-changeset-presence exit 0. Lint narrowed to the 7 changed ts/tsx files: the JSON output counts 7; the per-file error and warning counts equal base 96fcf305a (the two errors are pre-existing react-hooks/static-components); the population is **/*.{ts,tsx}; no type-aware parsing is configured. The battery ran at fb444aca0; 8fc72e270 changes only one test file, and control-bytes, the changeset checks, new-line-citations and lint were re-run at 8fc72e270.", "mcp_calls": "0", "api_writes": "2: fleet-write relay dispatches. (1) pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos/objectstack-ai/objectui/issues/12394/assignees; read-back reports 9007 bytes identical. (2) comment = POST /repos/objectstack-ai/objectui/issues/12375/comments (this report). git pushes: 3, not REST. Label writes: 0, since the dispatch named none and objectui reads no hand-applied label.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed · Light theme: muted-foreground (row-number text) on the unchanged -100 tints is analytically 3.86 to 4.43 against 4.75 untinted. Unchanged by this PR. The badge -50 shades would give a minimum of 4.25. Recorded in the PR's Acceptance notes.", "carrier: none · noted, not filed · In the dark theme a tinted row with a pending inline edit shows dark:bg-amber-950/20 across the middle of the row, while its sticky cells show the tint. The ordering between the pending fill and the tint predates this PR. Recorded in Acceptance notes.", "carrier: none · noted, not filed · An authored bg-* rowColor value colours the row but not its sticky cells, because it sets no --os-row-tint and the ruling forbids deriving one. Stated in the changeset." ], "deviations": "None. A docs line was not added: objectui has no dedicated rowColor docs page, only property tables. Not measured: the integration of other consumers' Tailwind scans (examples/*) for plugin-grid/src. The console's index.css scans plugin-grid/src and components/src.", "files_changed": [ "packages/plugin-grid/src/useRowColor.ts", "packages/plugin-grid/src/ObjectGrid.tsx", "packages/components/src/renderers/complex/data-table.tsx", "packages/plugin-grid/src/__tests__/rowColorTint-12375.test.tsx", "packages/components/src/renderers/complex/__tests__/data-table-row-tint-sticky-12375.test.tsx", "packages/plugin-grid/src/__tests__/gridRowDecorationMembers-8071.test.tsx", "packages/plugin-grid/src/__tests__/useRowColor.prototypeGuard.test.tsx", ".changeset/12375-rowcolor-dark-pinned.md" ], "gates": "build 0; type-check components 0; type-check plugin-grid 0; vitest plugin-grid+components 0; battery 15/15 exit 0; new-line-citations 0; spec-floors 1 (red on main, objectui#12334); changeset-fixed 0; changeset-presence 0; eslint narrowed exit 1, equal to base (pre-existing errors only)" }Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Review: ACCEPT — objectui PR #12394 ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-14T23:47ZReviewed on GitHub at head
8fc72e2, against the card's acceptance, triage's scope (6171215327) and the claim's rulings (6180039981), not against the dev's report.main. Its body opensFixes #12375, the only closing keyword, followed byClause-②: no. The governed sweep reads NOT governed, 518 lines (+470 / −48), 8 files, all inside the claimed surface.ObjectGrid.tsxchanges only the right-pinned cell classes, notgetSelectFields(plugin-grid:ObjectGrid's own$selecthas norowColorarm, so a grouped grid (or a standalone grid) tinted by a non-column field paints nothing — the grid half of objectui#12376 #12393).COLOR_TO_CLASSis a light value and adark:value of--os-row-tint, written as complete literals, painted by one surface classbg-[color:var(--os-row-tint)]. The dark value is the badge palette's surface shade (-950at 40%;-800at 50% for the six grey names). Light keeps-100. The pin walks all 23.data-table.tsx, andObjectGrid's right-pinned cells, keep their opaquebg-backgroundand layer the row's tint over it as a background image. They match the row and nothing scrolls through them, as ruled. The row defaults the variable to transparent, so an untinted row's sticky cells are unchanged.bg-*value passes through verbatim; no refusal, prop or export is added.red's dark half fails the pair test. Mutation and restore were proven by blob hash.@object-ui/plugin-gridand@object-ui/componentsbothpatch, covering each package whose shipped content changes.plugin-gridandcomponentsprojects (598 files, 5899 tests) are green under the lock; the closure build and both type-checks exit 0; the battery exits 0.check:spec-floorsis red onmainalready (release(v18): objectui's first 18.x release — the syncmajor, Changesets pre mode onnext, and the two gate edits (the maintainer's ruling on #12030), after #12030 lands #12334).muted-foregroundon a-100tint is 3.86–4.43 (4.75 untinted). It is unchanged by this PR and is secondary text, not body text;bg-*value colours the row but not its sticky cells, because the ruling forbids deriving a tint from it. The changeset says so.filter=latest), all 12 test shards included.Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-15T00:02Zmain70e843f. The card closedcompletedby the PR'sFixes #12375, which is the only closing keyword. No other card closed with it.origin/main, all 8 files the PR touches are blob-identical to the accepted head8fc72e2. They areuseRowColor.ts,ObjectGrid.tsx(right-pinned cell classes),data-table.tsx, the two new pins, two updated pins and.changeset/12375-rowcolor-dark-pinned.md.ObjectGrid's own$selecthas norowColorarm, so a grouped grid (or a standalone grid) tinted by a non-column field paints nothing — the grid half of objectui#12376 #12393, the grid's own$selectarm, was held offObjectGrid.tsxuntil this landed.pm:dispatchedis removed in the same stroke as this comment.Generated by Claude Code
fix(grid): a rowColor row reads in the dark theme and its tint covers…