Skip to content

feat(dashboard): table tile header separator and optional alternate row background#2519

Draft
alex-fedotyev wants to merge 6 commits into
mainfrom
alex/HDX-4601-table-tile-row-styling
Draft

feat(dashboard): table tile header separator and optional alternate row background#2519
alex-fedotyev wants to merge 6 commits into
mainfrom
alex/HDX-4601-table-tile-row-styling

Conversation

@alex-fedotyev

Copy link
Copy Markdown
Contributor

Two small readability improvements to the dashboard table tile, both verified in light and dark color modes.

HDX-4601

Summary

Add an always-on separator between a table tile's sticky header and its rows, and a new Alternate Row Background display setting (off by default) that zebra-stripes builder table tiles. The header separator makes the boundary clear as rows scroll under the sticky header (it was easy to lose in dark mode, where the header and first row sit close in luminance). The optional striping makes wide tables easier to scan across.

Changes

  • Header separator (always-on): an inset box-shadow on the sticky <thead> using the --color-border token. I used a shadow rather than a border-bottom because the table sets border-collapse: collapse, under which a collapsed border on a position: sticky header renders unreliably (it can scroll away with the cell content). The shadow paints on the sticky element, so it stays pinned.
  • Alternate Row Background (optional): a new optional alternateRowBackground boolean on the builder chart config (_ChartConfigSchema), surfaced in Display Settings for builder table tiles only, gated exactly like the sibling groupByColumnsOnLeft option. Off by default, so existing tiles are unchanged apart from the new separator. The field is z.boolean().optional(), and tiles are stored as Mixed, so an absent field reads as off with no migration.
  • New token --color-bg-table-stripe in both the hyperdx and clickstack themes (dark and light). I added a dedicated token instead of reusing --color-bg-muted: that token is also the row hover color, so reusing it would make the stripe and the hover identical and the hover invisible on striped rows.
  • Hover over a stripe: a striped row already sits a few percent off the body background, close enough to --color-bg-muted that the shared muted hover is nearly imperceptible on it (in both themes). Striped rows therefore use the stronger --color-bg-highlighted on hover, via a compound .tableRow.stripedRow:hover selector that outranks the existing hover rules, so the row under the cursor stays obvious.

Screenshots

Header separator only (default, no striping):

separator hyperdx light

separator hyperdx dark

Alternate Row Background enabled:

striped hyperdx light

striped hyperdx dark

Row hover stays visible over a stripe:

hover over stripe hyperdx light

Verified the same flows in the clickstack theme (light and dark) via Storybook.

Test plan

  • make ci-lint (eslint + tsc + stylelint on the changed files)
  • unit: HDXMultiSeriesTableChart.test.tsx (stripe class on odd rows when on, none when off), ChartDisplaySettingsDrawer.test.tsx (toggle shows for builder table only; Apply emits alternateRowBackground), DBTableChart.test.tsx (prop threads from a builder config; false for raw SQL)
  • full app unit suite (2091 pass) and common-utils suite (1340 pass)
  • visual: Storybook HDXMultiSeriesTableChart stories, light and dark, in both the hyperdx and clickstack themes; checked the separator, the striping, and hover over a striped row

[ui-states: allow] Styling-only change; the empty / loading / error states are rendered by existing DBTableChart branches and are untouched.
[viewport: allow] The separator and stripe are width-independent.

What's NOT in this PR (follow-up)

The new persisted field is exposed through the app save path only. External surface parity is tracked separately, matching how groupByColumnsOnLeft is exposed:

  • External REST / OpenAPI parity: HDX-4602
  • MCP dashboard schema + prompt parity: HDX-4603
  • Customer docs: DOC-855

…ow background

Add an always-on 1px separator between a table tile's sticky header and
its rows, and a new "Alternate Row Background" display setting (off by
default) that zebra-stripes builder table tiles. Both work in light and
dark color modes.

- Separator: inset box-shadow on the sticky thead using `--color-border`,
  so it stays pinned under `border-collapse: collapse` where a collapsed
  border would scroll away with the cell content.
- Striping: new optional `alternateRowBackground` boolean on the builder
  chart config, gated in Display Settings to builder table tiles (mirrors
  `groupByColumnsOnLeft`). Renders via a new `--color-bg-table-stripe`
  token defined in both the hyperdx and clickstack themes. Striped rows
  use the stronger `--color-bg-highlighted` on hover so the row under the
  cursor stays visible over a stripe.

HDX-4601

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jul 8, 2026 9:50pm
hyperdx-storybook Ready Ready Preview, Comment Jul 8, 2026 9:50pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 450d6d6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@hyperdx/app Patch
@hyperdx/common-utils Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label Jun 24, 2026
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Cross-layer change: touches frontend (packages/app) + shared utils (packages/common-utils)

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Production files changed: 9
  • Production lines changed: 154 (+ 286 in test files, excluded from tier calculation)
  • Branch: alex/HDX-4601-table-tile-row-styling
  • Author: alex-fedotyev

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds two readability improvements to dashboard table tiles: an always-on 1 px header separator (via box-shadow on the sticky thead to sidestep border-collapse rendering issues), and an opt-in alternateRowBackground zebra-striping setting that is off by default and preserved through the full config pipeline.

  • Adds alternateRowBackground: z.boolean().optional() to SharedChartSettingsSchema so both builder and raw SQL saved configs carry the field; groupByColumnsOnLeft intentionally stays on the builder-only _ChartConfigSchema.
  • Introduces a dedicated --color-bg-table-stripe token in both themes (dark + light) rather than reusing --color-bg-muted, and adds a .tableRow.stripedRow:hover compound CSS rule at specificity (0,3,0) to keep hover visible over striped rows.
  • Refactors ChartDisplaySettingsDrawer to split the old showGroupByColumnsOnLeft gate into showTableOptions (all table types, shows the new stripe toggle) and showBuilderTableOptions (builder only, shows the group-by ordering toggle).

Confidence Score: 5/5

Safe to merge — changes are purely additive and presentational; the new field defaults to absent/false so all existing tiles are unaffected.

All code paths (schema, serialization, form state, UI gate, CSS) are correctly wired and thoroughly tested. The box-shadow trick for the header separator is a well-known workaround for the border-collapse/sticky-header rendering bug. The new CSS token is distinct from the muted hover color by design, and the compound selector specificity is correctly calculated and documented. No data mutation, migration, or breaking change is involved.

packages/app/src/components/ChartEditor/tests/utils.test.ts — builder-path coverage for the new alternateRowBackground pick is absent; all other files look solid.

Important Files Changed

Filename Overview
packages/common-utils/src/types.ts Adds alternateRowBackground: z.boolean().optional() to SharedChartSettingsSchema so the field is available on both builder and raw SQL saved configs; also adds a clarifying comment explaining that groupByColumnsOnLeft intentionally stays on _ChartConfigSchema.
packages/app/src/HDXMultiSeriesTableChart.module.scss Adds .tableHeader (inset box-shadow separator), .stripedRow (stripe background), and .tableRow.stripedRow:hover (stronger hover for striped rows); specificity analysis in comments is correct — (0,3,0) outranks the existing (0,2,0) hover rules.
packages/app/src/HDXMultiSeriesTableChart.tsx Adds alternateRowBackground prop (default false), applies styles.tableHeader to thead, and conditionally adds styles.stripedRow to odd virtual-row indices; logic is correct and well-commented.
packages/app/src/components/ChartDisplaySettingsDrawer.tsx Refactors the single showGroupByColumnsOnLeft gate into showTableOptions (all table types) and showBuilderTableOptions (builder only), surfacing alternateRowBackground for SQL and builder tables, while keeping groupByColumnsOnLeft builder-only.
packages/app/src/components/DBTableChart.tsx Threads alternateRowBackground={!!queriedConfig.alternateRowBackground} to Table; the double-bang correctly converts undefined to false.
packages/app/src/components/ChartEditor/utils.ts Adds alternateRowBackground to the pick lists in all four code paths (PromQL save, SQL save, PromQL render, SQL render), correctly propagating the field through config conversion.
packages/app/src/theme/themes/hyperdx/_tokens.scss Adds --color-bg-table-stripe in both dark (4% white alpha) and light (3% black alpha) modes, correctly distinct from --color-bg-muted to preserve hover visibility on striped rows.
packages/app/src/theme/themes/clickstack/_tokens.scss Mirrors the hyperdx token additions for the clickstack theme in both dark (4% white) and light (3% black) modes.
packages/app/src/components/ChartEditor/tests/utils.test.ts Adds tests for alternateRowBackground in convertFormStateToSavedChartConfig and convertFormStateToChartConfig, but only for the SQL config path; the builder config path is not directly tested in this file (though covered by DBTableChart and types tests).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[SharedChartSettingsSchema\nalternateRowBackground?: boolean] --> B[SavedChartConfigSchema\nSQL & builder configs]
    B --> C[ChartEditor/utils.ts\nconvertFormStateToSavedChartConfig\nconvertFormStateToChartConfig]
    C --> D[EditTimeChartForm.tsx\ndestructure + setValue]
    D --> E[ChartDisplaySettingsDrawer\nshowTableOptions = Table\nshowBuilderTableOptions = Table && !sql]
    E --> F{configType}
    F -->|sql| G[alternateRowBackground toggle only]
    F -->|builder| H[groupByColumnsOnLeft toggle\n+ alternateRowBackground toggle]
    E --> I[onChange → config persisted]
    I --> J[DBTableChart.tsx\nalternateRowBackground = !!queriedConfig.alternateRowBackground]
    J --> K[Table component\nstyles.stripedRow on odd virtualRow.index]
    K --> L[HDXMultiSeriesTableChart.module.scss\n--color-bg-table-stripe\n.tableRow.stripedRow:hover → --color-bg-highlighted]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[SharedChartSettingsSchema\nalternateRowBackground?: boolean] --> B[SavedChartConfigSchema\nSQL & builder configs]
    B --> C[ChartEditor/utils.ts\nconvertFormStateToSavedChartConfig\nconvertFormStateToChartConfig]
    C --> D[EditTimeChartForm.tsx\ndestructure + setValue]
    D --> E[ChartDisplaySettingsDrawer\nshowTableOptions = Table\nshowBuilderTableOptions = Table && !sql]
    E --> F{configType}
    F -->|sql| G[alternateRowBackground toggle only]
    F -->|builder| H[groupByColumnsOnLeft toggle\n+ alternateRowBackground toggle]
    E --> I[onChange → config persisted]
    I --> J[DBTableChart.tsx\nalternateRowBackground = !!queriedConfig.alternateRowBackground]
    J --> K[Table component\nstyles.stripedRow on odd virtualRow.index]
    K --> L[HDXMultiSeriesTableChart.module.scss\n--color-bg-table-stripe\n.tableRow.stripedRow:hover → --color-bg-highlighted]
Loading

Reviews (5): Last reviewed commit: "fix(dashboard): persist alternateRowBack..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 228 passed • 3 skipped • 1426s

Status Count
✅ Passed 228
❌ Failed 0
⚠️ Flaky 0
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

The builder-table-only Display Settings block now holds two options
(Group By column ordering and Alternate Row Background) under one gate,
so `showGroupByColumnsOnLeft` no longer describes what it controls.
Rename it to `showBuilderTableOptions`. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alex-fedotyev

Copy link
Copy Markdown
Contributor Author

Addressed the naming point from the review in 39a99ac: renamed showGroupByColumnsOnLeft to showBuilderTableOptions, since that gate now controls both the Group By column ordering and the new Alternate Row Background toggle. No behavior change.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Deep Review

Scope: 14 files vs base 81e151c — dashboard table tile header separator (always-on) + optional off-by-default alternateRowBackground (zebra striping) display setting.
Mode: report-only (read-only; no edits, artifacts, or commits).
Verdict: Ready to merge. No correctness, type-safety, or maintainability defects. One recommended follow-up (external-surface parity) and minor test-coverage gaps.

✅ No critical issues found.

🟡 P2 — recommended

  • packages/api/src/utils/zod.ts:289 — The new persisted alternateRowBackground field is added to SharedChartSettingsSchema and wired end-to-end in the app, but is absent from the external REST table schemas (externalDashboardTableChartConfigSchema, externalDashboardTableRawSqlChartConfigSchema) and the MCP table tool schema, which are curated strip-by-default allowlists that deliberately mirror sibling fields like groupByColumnsOnLeft/backgroundChart; a UI-set value is silently dropped on external read and cannot be set via the external/agent surface. No breakage for existing consumers (unknown key stripped, not rejected) and the author scoped external parity to separate follow-up work, so this is a consistency gap rather than a regression.
    • Fix: When the deferred follow-up lands, add alternateRowBackground: z.boolean().optional() to both external table schemas in packages/api/src/utils/zod.ts and to the MCP config schema in packages/api/src/mcp/tools/dashboards/schemas.ts, and update the import/export pick() allowlists in packages/api/src/routers/external-api/v2/utils/dashboards.ts so the field round-trips in both directions.
    • project-standards, api-contract, agent-native
🔵 P3 nitpicks (1)
  • packages/app/src/HDXMultiSeriesTableChart.tsx:355 — The always-on header separator adds className={styles.tableHeader} to <thead>, but no test asserts the class is present, so an accidental removal of the separator would go uncaught.
    • Fix: Add a case in HDXMultiSeriesTableChart.test.tsx asserting the rendered <thead> carries the tableHeader class (trivially assertable under the identity-obj-proxy CSS-module mapping).

Reviewers (8): correctness, testing, maintainability, project-standards, agent-native, learnings-researcher, kieran-typescript, api-contract.

Testing gaps:

  • No test exercises striping under client-side sorting (raw SQL path enables it); stripe correctness after a column reorder is unverified.
  • No boundary test at exactly MAX_TABLE_ROWS with striping enabled (virtualization + odd/even parity at the cap).
  • Drawer coverage asserts the enabled→true path only; the toggle-off / omitted case (applyDefaultSettings ?? false) is not directly tested at the drawer level.
  • No form-state persistence round-trip test through convertFormStateToSavedChartConfig/convertSavedChartConfigToFormState.
  • The .tableRow.stripedRow:hover specificity guarantee (0,3,0 outranking 0,2,0) is documented in a comment but unverifiable in jsdom; it relies on visual/Storybook checks.

Comment on lines +285 to +290
<CheckBoxControlled
control={control}
name="alternateRowBackground"
size="xs"
label="Alternate Row Background"
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason we couldn't enable this option for sql tiles as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, no reason to keep it builder-only. The striping is purely presentational (it just tints alternating rendered rows), so it works the same on any table tile regardless of how the data was produced. I moved alternateRowBackground from the builder config onto the shared chart settings so raw SQL table configs persist it too, and split the gate here: Group By column ordering stays builder-only (it needs the builder select structure to know which columns are group-by keys), while Alternate Row Background now shows for every table tile. Done in e81b3ef.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, found a problem, need to fix

@kodiakhq kodiakhq Bot removed the automerge label Jul 7, 2026
@kodiakhq

kodiakhq Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

This PR currently has a merge conflict. Please resolve this and then re-add the automerge label.

The zebra striping is purely presentational (it tints alternating
rendered rows), so it applies to any table tile regardless of how the
data was produced. Move alternateRowBackground from the builder config
to SharedChartSettingsSchema so raw SQL table configs persist it too,
and split the display-settings gate: Group By column ordering stays
builder-only (it needs the builder select structure) while Alternate
Row Background shows for every table tile.
…e tiles

The form-state converters assemble raw SQL and PromQL configs from an
explicit field whitelist, and alternateRowBackground was not in it, so
toggling it on a SQL table tile did nothing and the setting was dropped
on save. Builder configs were unaffected because they spread the whole
form state. Add the field to the save and render pick lists so the
toggle persists and renders for every table tile.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants