Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

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

Closed

Description

@objectstack-fleet

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-pinned Actions column 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 no dark: partner. ObjectGrid.tsx:5897 hands that class to the table as rowClassName.
  • packages/components/src/renderers/complex/data-table.tsx (around line 3124): the row is cn("bg-background …", …, rowClassName(row)), so the authored class replaces bg-background in both themes. The row sets no foreground, so the text is the theme's foreground, which is light in the dark theme.
  • The same file's frozen cells paint their own bg-background (sticky left-0 z-10 bg-background at about lines 3179 and 3193, and isFrozen && 'sticky z-10 bg-background' at about line 3300). That is the hole in the tint. The right-pinned actions column does the same through rightPinnedClasses (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_MAP in packages/fields/src/index.tsx (around line 1428) uses bg-red-50 … dark:bg-red-950/40. The data table's own unsaved-row fill uses bg-amber-50 dark:bg-amber-950/20, and its error fill uses bg-destructive/10 dark:bg-destructive/15.

Fix direction (for the implementer to measure, ⛔ not a ruling)

  • Make every COLOR_TO_CLASS value a complete light and dark literal pair on the same surface shades the badge palette uses, for example red: '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, as packages/plugin-detail/src/headerColor.ts documents (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 the rowColor docs could say so in one line.

Acceptance

  • In both themes, the text and badges on a rowColor row 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

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · bug · priority:p2 · domain:ui · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T14:52Z

    Read fresh.

    • priority:p2: a measured readability defect. In the dark theme a rowColor row keeps its light bg-*-100 fill under light text, so the row's content is close to unreadable. The tint also skips the pinned columns. Every grid with rowColor is affected under the dark theme; nothing is lost or written wrong.
    • Lane domain:ui (packages/plugin-grid and packages/components renderers/complex/data-table.tsx).
    • Scope: a dark pair for each colour class (as the badges already carry), the tint reaching pinned columns, and pins in both themes.
  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 14, 2026
  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12375-rowcolor-dark-pinned
    Worktree: objectui-issue-12375
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/plugin-grid/src/useRowColor.ts, packages/plugin-grid/src/ObjectGrid.tsx (the right-pinned actions cell classes and the rowClassName hand-off only), packages/components/src/renderers/complex/data-table.tsx (the row and frozen-cell classes), their tests, the rowColor docs 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/objectui reads 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 Cases
    Thread-read: 6171215327
    Serial constraints cleared: objectui#12376 (PR #12392, packages/plugin-list/src/ListView.tsx) is disjoint from this surface. objectui#12393 (ObjectGrid.tsx getSelectFields) is filed, not queued or claimed; this claim touches only the pinned-cell classes in that file. objectui#12326 is in packages/app-shell.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — objectui PR #12394 · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-14T23:47Z

    Reviewed 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.

    • Shape: the PR is a draft against main. Its body opens Fixes #12375, the only closing keyword, followed by Clause-②: no. The governed sweep reads NOT governed, 518 lines (+470 / −48), 8 files, all inside the claimed surface. ObjectGrid.tsx changes only the right-pinned cell classes, not getSelectFields (plugin-grid: ObjectGrid's own $select has no rowColor arm, so a grouped grid (or a standalone grid) tinted by a non-column field paints nothing — the grid half of objectui#12376 #12393).
    • Acceptance ③, every name a pair: each of the 23 names in COLOR_TO_CLASS is a light value and a dark: value of --os-row-tint, written as complete literals, painted by one surface class bg-[color:var(--os-row-tint)]. The dark value is the badge palette's surface shade (-950 at 40%; -800 at 50% for the six grey names). Light keeps -100. The pin walks all 23.
    • Acceptance ②, the whole row: the checkbox, row-number and frozen cells in data-table.tsx, and ObjectGrid's right-pinned cells, keep their opaque bg-background and 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.
    • Acceptance ①, readability: measured in Chromium against real Tailwind 4.3.3 output, the lowest body-text contrast on a tinted row is 16.80 in dark (1.03 before) and 16.24 in light (unchanged), both well over 4.5:1. The sticky cell equals the row within 1/255 per channel on all 23 names in both themes. The theme foreground is kept, as ruled.
    • Rulings kept: an authored bg-* value passes through verbatim; no refusal, prop or export is added.
    • Ablation: removing the sticky tint from the frozen cell fails 2 tests across both new files; deleting red's dark half fails the pair test. Mutation and restore were proven by blob hash.
    • Changeset: @object-ui/plugin-grid and @object-ui/components both patch, covering each package whose shipped content changes.
    • Evidence: the full plugin-grid and components projects (598 files, 5899 tests) are green under the lock; the closure build and both type-checks exit 0; the battery exits 0. check:spec-floors is red on main already (release(v18): objectui's first 18.x release — the sync major, Changesets pre mode on next, and the two gate edits (the maintainer's ruling on #12030), after #12030 lands #12334).
    • Acceptance notes (not filed, all predate this PR or follow a ruling):
      • in light, the row number's muted-foreground on a -100 tint is 3.86–4.43 (4.75 untinted). It is unchanged by this PR and is secondary text, not body text;
      • in dark, a tinted row with a pending inline edit shows the amber pending fill in the middle cells and the tint in the sticky cells. That ordering predates this PR;
      • an authored bg-* value colours the row but not its sticky cells, because the ruling forbids deriving a tint from it. The changeset says so.
    • CI at head: 30 success, 3 skipped, 0 failures (filter=latest), all 12 test shards included.

    Next: pr_ready + automerge_enable.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-15T00:02Z


    Generated by Claude Code

  7. added a commit that references this issue on Oct 28, 2026
    70e843f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions