Skip to content

feat(app): add a row-click preference to expand result rows inline - #3117

Merged
kodiakhq[bot] merged 10 commits into
mainfrom
cursor/expand-log-inline-on-row-click-d7eb
Sep 29, 2026
Merged

kodiakhq[bot] merged 10 commits into
mainfrom
cursor/expand-log-inline-on-row-click-d7eb

Conversation

@MikeShi42

@MikeShi42 MikeShi42 commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Resolves HDX-5365.

Stacked on #3171. Base is cursor/fix-virtualized-row-height-d7eb; it will retarget to main once that merges. See "Virtualized row heights" below.

Why

The only way to expand a search result row inline is the 16px chevron, which is a poor hit target when you are scanning logs during an incident. Comparable tools expand on row click.

Rather than change what a row click does for everyone, this adds a preference. The default is unchanged — a row click still opens the side panel. Users who want the bigger hit target can opt in.

What changed

Preferences → Search results → Row click, defaulting to Open side panel. Set it to Expand inline and:

  • a row click toggles inline expansion instead of opening the side panel;
  • the side panel moves to a hover button on the row (Open in side panel, beside the existing copy buttons), and the expanded row's maximize button still opens it;
  • row clicks keep driving the side panel while it is open, so browsing row to row inside the panel is unchanged. Closing the panel returns clicks to inline expansion.

Stored as an optional rowClickAction. Stored preferences that predate the field are returned as-is rather than merged with the defaults, so readers fall back to DEFAULT_ROW_CLICK_ACTION.

Two table variants need care regardless of the preference:

  • Tables that hide the expand column (surrounding context, patterns) have nowhere to put inline details, so they always open the side panel.
  • The ClickHouse dashboard's slowest-queries list renders inline details but passes no onRowDetailsClick, so it always expands inline and offers neither the hover button nor the expanded row's maximize button — both would only write URL params nothing reads.

Routing a row click needs to know whether a side panel is available and whether one is already open, and two signals did not actually mean that:

  • highlightedLineId was passed to the table even when the panel was not mounted. rowWhere/rowSource survive a source switch while closing the panel clears both, so a stale rowWhere left the table highlighting a row with no panel on screen. The panel is now hoisted into one sidePanel element that both its own mount and highlightedLineId derive from.
  • ExpandedLogRow gated its maximize button on {openSidebar && …}, the return value of a useCallback and so always truthy. It now takes canOpenSidePanel.

DBRowTableIconButton also gained an aria-label from its title, which the row hover buttons had been missing.

Virtualized row heights

Reported against this branch: expand and collapse a few rows, scroll, and the table goes blank under the header.

That bug is on main today — it reproduces identically through the chevron, with this PR's packages/app/src reverted. A row and its inline expansion are two trs sharing one virtual index, and both were measured, so the expanded one took over that index's ResizeObserver registration and left its height cached there after collapsing. Every expand/collapse shrank the render window.

Fixed separately in #3171 so it lands for chevron users regardless of this PR, and merged in here so this branch does not ship the bigger hit target on top of it. That merge is the only reason DBRowTable.tsx now renders a tbody per row.

Tests

  • Seven unit tests in DBRowTable.test.tsx: the side panel on row click by default (and no redundant hover button in that mode), then under expand — inline expand/collapse on body click, the hover button, showExpandButton={false}, and the open-panel case; plus the no-side-panel table expanding inline regardless of the preference. These are the first tests in that file to render virtualized body rows, hence the geometry mocks.
  • The inline-expand test also asserts the expanded row exposes Open in side panel and that clicking it writes the rowWhere param, so canOpenSidePanel is pinned in both directions — hardcoding it to false fails the test rather than staying green.
  • UserPreferencesModal.test.tsx drives the select: the default for users with no stored preference, a stored value round-tripping into the control, and picking an option writing rowClickAction. A wrong storage key or option value now fails instead of passing green.
  • E2E: the specs that reach the side panel go back to a plain row click, since that is the default again. A new Row click set to expand inline describe seeds the preference before navigating and asserts a row body click expands inline without opening the panel, collapses on a second click, and that the hover button opens the panel.
  • virtualized-row-heights.spec.ts comes in from fix(app): stop expanded rows corrupting virtualized row heights #3171 and covers the virtualizer regression.

Rebase onto main

Rebased past #3140 and #3164, which added row multi-select and hover-revealed selection checkboxes to the same row component. No textual conflicts, and the two features compose: the checkbox lives in its own cell and stops propagation, so it does not compete with the row-body click.

CI lint was failing only because this branch predated the --max-warnings bump on main. The rebase picks that up; the one warning this PR did add (an unsafe cast in the new Select handler) is gone, so the app count is back under the ceiling rather than exactly on it.

Verification: make ci-lint and make ci-unit green (244 suites, 3948 tests after the merge). All 94 search/ E2E tests re-run — 85 passed, 1 flaky-then-passed, and 2 search-filters.spec.ts failures that reproduce identically with this PR's packages/app/src reverted to origin/main, so they are environmental and not from this change. correlated-metric-source, span-links and traces-workflow also pass.

Walkthrough

The reported repro, end to end on this branch: two row clicks opening the side panel on the default setting, switching the preference to Expand inline, expanding and collapsing six rows, then scrolling — the table stays full.

virtualized_scroll_repro_fixed.mp4

Default — a row click opens the side panel, unchanged:

Log search with the row side panel open showing Overview, Column Values, Surrounding Context and Infrastructure tabs

The opt-in:

Preferences modal showing the Search results section with Row click set to Expand inline

With it on, a row click expands in place, with no side panel:

A result row expanded inline showing its Column Values tab, with the rest of the table still visible below

Hovering a row shows both features side by side — the selection checkbox on the left, and this PR's Open in side panel button among the row action icons on the right:

A hovered result row showing the multi-select checkbox at the left and the row action icons including Open in side panel at the right

Linear Issue: HDX-5365

Open in Web Open in Cursor 

@changeset-bot

changeset-bot Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 466bca7

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

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Minor
@hyperdx/api Minor
@hyperdx/otel-collector Minor

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

@vercel

vercel Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Sep 29, 2026 5:05pm UTC
hyperdx-storybook Ready Ready Preview Sep 29, 2026 5:05pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Adds a user preference for how clicking table rows behaves.

The PR is not yet safe to merge because dashboard and chart-editor E2E helpers still query data rows using selectors invalidated by the new virtualized-row DOM.

Fix All in Claude CodeFindings

  1. P1 Default preserves old behavior ▶
  2. P1 Virtual row selectors break ▶

Summary

Adds an opt-in preference that makes result-row clicks toggle inline expansion while preserving side-panel navigation as the default.

  • Routes row clicks according to the preference, side-panel availability, and current panel state.
  • Adds side-panel actions for inline-expanded rows and updates accessibility labels.
  • Groups each virtualized row and its details under one measured tbody.
  • Adds unit and E2E coverage for preferences and row-click behavior.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Result row clicked] --> B{Side panel already open?}
    B -->|Yes| C[Open selected row in side panel]
    B -->|No| D{Inline details supported?}
    D -->|No| C
    D -->|Yes| E{Side panel available?}
    E -->|No| F[Toggle inline expansion]
    E -->|Yes| G{Row-click preference}
    G -->|Open side panel| C
    G -->|Expand inline| F
Loading

Reviews (9) · Last reviewed commit: "Merge branch 'main' into cursor/expand-l..."

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

E2E Test Results

✅ All tests passed • 387 passed • 1 skipped • 1461s

Status Count
✅ Passed 387
❌ Failed 0
⚠️ Flaky 1
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

🔵 Tier 2 — Low Risk

Small, isolated change with no API route or data model modifications.

Why this tier:

  • Standard feature/fix — introduces new logic or modifies core functionality

Review process: AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns.
SLA: Resolve within 4 business hours.

Stats
  • Production files changed: 8
  • Production lines changed: 158 (+ 389 in test files, excluded from tier calculation)
  • Branch: cursor/expand-log-inline-on-row-click-d7eb
  • Author: MikeShi42

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

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found. This is an opt-in preference (default behavior is unchanged: a row click still opens the side panel), the type wiring is sound, and the new RawLogTable decision matrix is internally consistent across all consumers. Unit + E2E coverage for the new happy paths is strong. The prior "virtual row selectors break" P1 does not apply to this diff: the data-index-on-<tbody> shape and the tbody tr[data-index] helpers in DashboardPage.ts / ChartEditorComponent.ts both already exist on the base commit and are untouched here — it is pre-existing, not introduced. The recommendations below are non-blocking.

🟡 P2 -- recommended

  • packages/app/src/components/ExpandableRowTable.tsx:64 -- The expanded-row maximize button opens the side panel via useSidebarOpener, which writes only the rowWhere/rowSource URL params and never populates aliasWith; for sources whose row-identity WHERE needs WITH clauses derived from aliased SELECT expressions, the panel query can reference undefined aliases and resolve the wrong/empty row, and the expand preference promotes this button to a primary path to the panel (pre-existing mechanism, higher reachability here).
    • Fix: Route the maximize button through the same handler that sets aliasWith (e.g. onRowDetailsClick/onOpenSidebar), or thread aliasWith into the opener so the deep-link path matches the row-click path.
    • correctness, adversarial
  • packages/app/src/components/DBSqlRowTableWithSidebar.tsx:146 -- The new source-switch branch — where a stale rowSource !== sourceId nulls sidePanel and thus highlightedLineId, re-enabling inline expansion — is the one behavioral branch this diff introduces with no test in either direction; a regression making sidePanel unconditionally truthy would pass every current test.
    • Fix: Add a DBSqlRowTableWithSidebar (or DBSearchPage) test mounting with a rowWhere/rowSource from a different source than sourceId, asserting no panel renders and highlightedLineId is undefined, plus the matching same-source case.
    • testing, maintainability, kieran-typescript, adversarial
🔵 P3 nitpicks (6)
  • packages/app/src/components/DBSqlRowTableWithSidebar.tsx:146 -- highlightedLineId is derived from the truthiness of a JSX.Element | null (sidePanel ? ... : undefined), coupling "should the panel render" to "should the table treat the panel as open"; wrapping sidePanel in a Fragment or a loading branch would silently break both highlighting and inline-expand suppression.
    • Fix: Extract a named boolean (e.g. const isSidePanelOpen = Boolean(sourceData && (rowSource === sourceId || !rowSource))) and use it for both the render guard and highlightedLineId.
    • maintainability, kieran-typescript
  • packages/app/src/components/DBRowTable.tsx:1301 -- In expand mode with the panel already open, the row-body click already drives the panel (expandInlineOnRowClick is false), yet the hover "Open in side panel" button still renders and performs the identical action, since onOpenSidePanel is gated on prefersInlineExpand && canOpenSidePanel without the open-state check.
    • Fix: Add && highlightedLineId == null to the onOpenSidePanel gate so the button hides once the panel is open.
    • adversarial
  • packages/app/src/useUserPreferences.tsx:57 -- isUserPreferences validates only colorMode, so a corrupt/out-of-enum persisted rowClickAction is returned typed as a valid RowClickAction; it degrades gracefully today (blank Mantine Select, falls through to sidePanel) but violates the declared union at runtime.
    • Fix: Validate or clamp rowClickAction to 'expand' | 'sidePanel' at read time, resetting to the default otherwise.
    • api-contract
  • packages/app/src/components/DBRowTable.tsx:476 -- Row-click routing reads the async URL-derived highlightedLineId with no synchronous intent latch, so in expand mode a rapid second click during the panel open/close propagation window is misrouted (expands instead of moving the panel, or reopens instead of expanding); it self-corrects on the next click and is confined to the opt-in mode.
    • Fix: Latch the open/close intent synchronously in a local ref set by the open/close handlers rather than reading the URL value for the routing decision.
    • julik-frontend-races
  • packages/app/src/UserPreferencesModal.tsx:126 -- The new "Row click" Select has no programmatic accessible name (the visible "Row click" text is unassociated plain text), so its only handle is the option labels and data-testid; consistent with the sibling Selects but still a gap.
    • Fix: Add aria-label="Row click" to the Select.
    • ce-agent-native-reviewer
  • packages/app/src/components/DBRowTable.tsx:1210 -- The row-hover action cluster (now including the added "Open in side panel" button) is rendered as role="button" descendants inside the row-body <button>, which is invalid HTML and may not reliably expose the nested controls in the accessibility tree; the structure predates this PR but it adds one more nested control.
    • Fix: Render the hover action cluster as an absolutely-positioned sibling of the row-body <button> rather than nested inside it.
    • ce-agent-native-reviewer

Reviewers (11): correctness, adversarial, testing, maintainability, project-standards, api-contract, kieran-typescript, julik-frontend-races, previous-comments, agent-native, learnings-researcher.

Testing gaps:

  • No test for the DBSqlRowTableWithSidebar source-switch stale-param branch (panel null → highlightedLineId undefined → inline expansion re-enabled).
  • No test opening the panel via the maximize/deep-link path for a source with aliased columns, which would expose the aliasWith gap.
  • No test for a corrupt/out-of-enum persisted rowClickAction confirming graceful fallback to sidePanel.
  • The showExpandButton={false} case asserts the row click opens the panel but not that the hover "Open in side panel" button is absent.

Note: clickRow/clickFirstRow were reintroduced into packages/app/tests/e2e/components/TableComponent.ts by the merge from main; benign under the default sidePanel preference, but a future spec that enables the expand preference and then calls them would toggle inline expansion instead of opening the panel.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

* Click on a specific row. On tables that support inline expansion this
* expands the row in place; use {@link openRowSidePanel} for the side panel.
*/
async clickRow(index: number) {

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.

🔵 minor — clickRow / clickFirstRow now have zero callers and silently mean the opposite of what specs used them for

After this diff every call site moved to openRowSidePanel / clickRowBody, so both helpers are unreferenced across the repo (grep for clickRow(/clickFirstRow( outside this file returns only MetricExplorer.test.tsx's unrelated local helper). They are now a trap: a future spec calling clickFirstRow to reach the side panel gets inline expansion instead — or the side panel, depending on whether rowWhere happens to be set. Delete both and let clickRowBody / openRowSidePanel be the API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed and done in 85271bb — both helpers are deleted. Confirmed the only remaining clickRow( hits are MetricExplorer.test.tsx's unrelated local helper, so clickRowBody / openRowSidePanel are now the whole API.

// Once the side panel is open it stays the active surface, so clicks keep
// moving it from row to row instead of expanding rows behind it.
const expandInlineOnRowClick =
prefersInlineExpand && (!canOpenSidePanel || highlightedLineId == null);

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.

🔵 minor — highlightedLineId != null is a proxy for "the side panel is open", but it is non-null in states where no panel renders

highlightedLineId comes from DBSqlRowTableWithSidebar as rowId ?? undefined (the URL rowWhere param), while the panel itself only renders when sourceData && (rowSource === sourceId || !rowSource) (DBSqlRowTableWithSidebar.tsx:119). DBSearchPage never clears rowWhere/rowSource when the source changes, so landing on the search page with a stale ?rowWhere=…&rowSource=<other-source> shows no panel yet disables inline expansion — the first row click opens the panel instead of expanding. Derive the flag from whether the panel is actually mounted (e.g. pass an explicit isSidePanelOpen prop from DBSqlRowTableWithSidebar, which already computes that condition) rather than from highlightedLineId.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Confirmed and fixed in 85271bb. The reachable path is switching source with a row panel open: onCloseSidebar clears both params, but a source switch clears neither, so rowSource goes stale, the panel unmounts, and rowWhere survives.

I fixed it in DBSqlRowTableWithSidebar rather than by adding an isSidePanelOpen prop, since the condition only needs to exist once. The panel is hoisted into a sidePanel element and both the mount and the highlight read from it:

const sidePanel =
  sourceData && (rowSource === sourceId || !rowSource) ? (
    <DBRowSidePanel ... />
  ) : null;
...
{sidePanel}
<DBSqlRowTable highlightedLineId={sidePanel ? (rowId ?? undefined) : undefined} ... />

This keeps sourceData narrowed (a plain boolean would not), avoids duplicating the predicate, and needs no new prop on DBRowTable. It also fixes the pre-existing half of the bug you did not mention: the table was highlighting a row whose panel was not on screen.

onClick={() => openSidebar(rowId, source?.id)}
title="Open in sidebar"
aria-label="Open in sidebar"
title="Open in side panel"

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.

🔵 minor — Expanded rows still offer "Open in side panel" on tables that have no side panel

This PR deliberately hides the row hover button when onRowDetailsClick is absent so the ClickHouse dashboard's slow-query list doesn't "offer a dead action" (DBRowTable.tsx:1888), but ExpandedLogRow unconditionally renders the maximize button — and ClickhousePage.tsx:800 renders no DBRowSidePanel at all (no DBRowSidePanel/rowWhere reference anywhere in that file), so clicking it just writes rowWhere/rowSource into the URL and nothing opens. Now that a plain row click reaches this expanded row there, gate the button on the same signal — e.g. thread the table's canOpenSidePanel into ExpandedLogRow — instead of the always-truthy {openSidebar && …} guard on line 58.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correct, and fixed in 85271bb. {openSidebar && ...} guarded on the return value of a useCallback, so it was always truthy — ExpandedLogRow now takes canOpenSidePanel and DBRowTable passes the same flag that gates the row hover button, so the two agree.

Worth noting this was reachable before this PR too (the chevron already expanded rows on that table), just much harder to stumble into. Extended the no-side-panel unit test to assert the button is absent after expanding, not only before.

description="What clicking a result row does. The chevron always expands inline."
>
<Select
data-testid="row-click-action-select"

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.

🔵 minor — The new preference control is untested and its data-testid has no consumer

The seven new unit tests seed hdx-user-preferences in localStorage directly, so nothing exercises the modal half of the feature: a regression here (wrong key, wrong option value, onChange dropped) would leave every test green. data-testid="row-click-action-select" is referenced nowhere in the repo — it looks like a hook for a test that wasn't written. Either add a test that drives this Select and asserts the stored preference, or drop the orphan test id.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Right on both counts — the testid was left over from an E2E spec I ended up deleting. Rather than drop it, 85271bb adds src/__tests__/UserPreferencesModal.test.tsx, which drives the Select and covers the wiring you flagged:

  • shows Expand inline when nothing is stored (the DEFAULT_ROW_CLICK_ACTION fallthrough for existing users)
  • reflects a stored sidePanel (the value binding)
  • picking Open side panel writes rowClickAction: 'sidePanel' under hdx-user-preferences (the storage key, option values, and onChange)

A wrong key or option value now fails rather than passing green, and the testid has a consumer.

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

3 finding(s): 🔴 0 critical · 🟠 1 major · 🔵 2 minor

3 posted as inline comment(s) on the changed lines.


Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

<IconLink size={16} />
</DBRowTableIconButton>
{onOpenSidePanel && (
<DBRowTableIconButton

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.

🟠 major — The side panel's only remaining direct entry point is unreachable by keyboard and hidden from screen readers

DBRowTableIconButton renders a div with role="button" and tabIndex={-1} (DBRowTableIconButton.tsx:56), and .rowButtons is opacity: 0 until hover (packages/app/styles/LogTable.module.scss:164). It is also nested inside the row-body <button> (DBRowTable.tsx:1155), whose descendants are presentational per ARIA, so the new aria-label is never exposed. Before this PR the row body itself — a real focusable button — opened the panel; now keyboard/AT users can only reach it via expand-then-maximize. Render the side-panel control outside the row <button> (e.g. alongside the chevron in the expand cell, or as its own <td>) as a focusable element, rather than as another tabIndex={-1} hover affordance.


// Inline expansion is owned by the expand-button column, so tables that hide
// it (surrounding context, patterns) always fall back to the side panel.
const prefersInlineExpand =

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.

🔵 minor — A preference labelled "Search results" silently changes row-click behavior for every row table in the app

prefersInlineExpand is computed in RawLogTable, so the new default applies to every DBSqlRowTableWithSideBar consumer — dashboard Search tiles (DBDashboardPage.tsx:1390), ServiceDashboardSlowestEventsTile.tsx:92, PodDetailsSidePanel.tsx:203, NodeDetailsSidePanel.tsx:194, NamespaceDetailsSidePanel.tsx:181, llm/dashboard/ErrorsTab.tsx:113 and ChartPreviewPanel.tsx:315 — several of which are fixed-height tiles where .expandedRowContent's 400px panel (LogTable.module.scss:61) fills the whole tile, and one of which is already inside a side panel. Either scope the default to the search page with an explicit prop on DBSqlRowTableWithSideBar (defaulting the tiles to sidePanel), or drop the "Search results" framing in UserPreferencesModal.tsx:120 so the setting reads as global.

sourceId={sourceId}
onRowDetailsClick={onOpenSidebar}
highlightedLineId={rowId ?? undefined}
highlightedLineId={sidePanel ? (rowId ?? undefined) : undefined}

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.

🔵 minor — The stale-rowWhere guard that gates inline expansion has no test

The seven new unit tests all drive RawLogTable with highlightedLineId passed in directly, so nothing covers the wiring this PR actually changed here: that a rowWhere left over from another source (rowSource !== sourceId) no longer mounts a panel, no longer highlights a row, and no longer suppresses inline expansion. Add a DBSqlRowTableWithSidebar test that mounts with rowWhere set and rowSource pointing at a different source, and asserts the row body still toggles inline expansion.

@cursor

cursor Bot commented Sep 14, 2026

Copy link
Copy Markdown

Addressed the review. Three of the four inline findings were real and are fixed in 85271bb; I declined the tabIndex one with reasoning inline.

Fixed — all three were the same root cause, inferring "a side panel is available/open" from a signal that does not mean that:

  • highlightedLineId was passed to the table even when the panel was not mounted. Since rowWhere/rowSource survive a source switch, a stale rowWhere left the table highlighting a row with no panel on screen and suppressing inline expansion, so the first click after switching source opened the panel instead of expanding. Both now derive from a single hoisted sidePanel element, so there is no second copy of the predicate and no new prop.
  • ExpandedLogRow gated its maximize button on {openSidebar && ...} — the result of a useCallback, always truthy. On the ClickHouse dashboard's slow-query list it wrote URL params nothing reads. It now takes canOpenSidePanel, matching the row hover button.
  • clickRow/clickFirstRow had lost their last callers and would silently expand inline for a future spec reaching for the side panel. Deleted.

Declined — tabIndex={0} on the Open in side panel hover button. .rowButtons is opacity: 0 until hover with no :focus-within reveal, so that adds an invisible tab stop per rendered row on a virtualized table, with no visible focus ring. The keyboard path to the panel already exists and lands on a visible control: Tab to row → Enter (expand) → Tab → Enter on the expanded row's always-visible Open in side panel button. Details in the thread. Same reasoning for the P3 about applying it to the copy/link/wrap siblings, which is a pre-existing question about all four buttons.

Also skipped the P3 about extracting the virtualizer geometry mocks into a shared helper — one consumer does not justify the indirection.

Verification: make ci-lint passes; app unit suite is 236 files / 3860 tests green, including the new modal test and the 35 in DBRowTable.test.tsx. All 75 search/ E2E tests were re-run: 71 passed, 2 flaky-then-passed, and the 2 search-filters.spec.ts failures are the pre-existing ones that fail identically on base commit 3876d6b9 and involve no row clicks. select-star-error-state.spec.ts flaked on an async query race under parallelism and passes in isolation.

/** What clicking the body of a search result row does. */
export type RowClickAction = 'expand' | 'sidePanel';

export const DEFAULT_ROW_CLICK_ACTION: RowClickAction = 'sidePanel';

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.

P1 Default preserves old behavior

Users without a stored rowClickAction, including existing users whose preferences predate this field, fall back to sidePanel. Because DBRowTable enables inline expansion only when this value is expand, row clicks keep opening the side panel unless users manually opt in. This contradicts the PR's stated behavior that row clicks expand inline by default and that existing users receive the new default.

Suggested change
export const DEFAULT_ROW_CLICK_ACTION: RowClickAction = 'sidePanel';
export const DEFAULT_ROW_CLICK_ACTION: RowClickAction = 'expand';

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex


// Once the side panel is open it stays the active surface, so clicks keep
// moving it from row to row instead of expanding rows behind it.
const expandInlineOnRowClick =

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.

🔵 minor — Opening the side panel strands already-expanded rows: the body button stops collapsing them and drops aria-expanded

expandInlineOnRowClick is global to the table, but isExpanded is per row. With rowClickAction: 'expand', click row A's body (it expands, ExpandedLogRow renders because line 1256 gates only on showExpandButton && isExpanded), then click row B's "Open in side panel" hover button. highlightedLineId becomes non-null, so for row A the body button now reports aria-expanded={undefined} and is labelled "View details for log entry" while row A is still visibly expanded, and clicking it re-targets the panel instead of collapsing. Gate per row — e.g. keep toggling when isExpanded is true (expandInlineOnRowClick || isExpanded) so an expanded row can always be closed by the control that opened it. (Same state breaks the E2E clickRowBody regex in tests/e2e/components/TableComponent.ts:106.)

/** What clicking the body of a search result row does. */
export type RowClickAction = 'expand' | 'sidePanel';

export const DEFAULT_ROW_CLICK_ACTION: RowClickAction = 'sidePanel';

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.

🔵 minor — DEFAULT_ROW_CLICK_ACTION is a second source of truth every reader has to remember

storageWithMigration.getItem returns the parsed object as-is (line 182–183) rather than {...DEFAULT_PREFERENCES, ...migrated}, which is why the new field needs ?? DEFAULT_ROW_CLICK_ACTION repeated at every read site (DBRowTable.tsx:458, UserPreferencesModal.tsx:127) — a third reader that forgets it silently gets undefined. Merging DEFAULT_PREFERENCES into the value returned by getItem would let rowClickAction: 'sidePanel' live in DEFAULT_PREFERENCES alone, and would also stop partially-stored preferences from yielding undefined timeFormat/font (which is exactly what the new tests and the new E2E seed do).

// outlive the panel it belongs to. Deriving the table's highlight from this
// element keeps it from highlighting a row — and from treating the panel as
// open, which suppresses inline expansion — while no panel is mounted.
const sidePanel =

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.

🔵 minor — The stale-rowWhere fix — the subtler of the two bug fixes — has no test

Nothing covers that a leftover rowWhere with a rowSource pointing at a different source now yields highlightedLineId={undefined} instead of highlighting a phantom row and suppressing inline expansion. The DBRowTable tests only cover the consumer side (highlightedLineId="some-other-row"). Add a test that renders DBSqlRowTableWithSideBar with sourceId="B" while rowWhere/rowSource are set for source A and asserts the child table receives no highlightedLineId — otherwise a future refactor of that predicate reintroduces the bug green.


// A fresh jotai store per render keeps the stored preference from leaking
// into the next test through the module-level preferences atom.
const renderTable = (ui: ReactElement, rowClickAction?: RowClickAction) => {

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.

🔵 minor — renderTable seeds localStorage but never clears it after the describe

window.localStorage.clear() runs only at the start of each render, so after the last "Row click behavior" test the key hdx-user-preferences is left at {colorMode:'dark',rowClickAction:'expand'} for the rest of the file; the "Error state" describe then mounts RawLogTable on jotai's module-level default store, whose atomWithStorage onMount reads that value (so isUTC/timeFormat/font are undefined there). The comment on line 262 says the fresh Provider prevents leakage, but the leak is via localStorage, not the store. Add an afterEach(() => window.localStorage.clear()) to this describe.

@cursor cursor Bot changed the title feat(app): expand result rows inline on row click feat(app): add a row-click preference to expand result rows inline Sep 16, 2026
cursoragent and others added 5 commits September 21, 2026 06:40
Clicking a row body opened the side panel, leaving the 16px chevron as the
only way to expand inline - a poor hit target during an incident, and the
opposite of what comparable tools do.

Row clicks now toggle inline expansion. The side panel moves to a row hover
button, still stays one click away from an expanded row, and keeps receiving
row clicks while it is open so browsing row to row is unchanged. Preferences
-> Search results -> Row click restores the old behavior.

Co-authored-by: Mike Shi <mike@hyperdx.io>
RawLogTable is also used by the ClickHouse dashboard's slow-query list,
which passes no onRowDetailsClick. There the side panel preference had
nothing to open, so a row click did nothing and the hover button offered
a dead action. Fall back to inline expansion when the handler is absent.

Co-authored-by: Mike Shi <mike@hyperdx.io>
Three review findings, all the same root cause — the code inferred "a side
panel is available/open" from signals that do not mean that:

- `highlightedLineId` was passed even when the panel was not mounted. Because
  `rowWhere`/`rowSource` survive a source switch, a stale `rowWhere` left the
  table highlighting a missing row and suppressing inline expansion, so the
  first click after switching source opened the panel instead of expanding.
  Both now derive from the panel element.
- `ExpandedLogRow` guarded its maximize button on `{openSidebar && ...}`, the
  result of a `useCallback` and so always truthy. On the ClickHouse dashboard's
  slow-query list, which renders no panel, the button only wrote URL params
  nothing reads. It now takes `canOpenSidePanel`, matching the row hover button.
- `clickRow`/`clickFirstRow` lost their last callers and would silently expand
  inline for a future spec reaching for the side panel. Removed in favour of
  `clickRowBody`/`openRowSidePanel`.

Also covers the preferences Select, whose stored key and option values no test
exercised.

Co-authored-by: Mike Shi <mike@hyperdx.io>
Inline expansion becomes opt-in rather than the new default, so nothing changes
for anyone who does not go looking for it.

`DBRowTable`'s routing already read the preference, so only the constant moves.
The fallout is in the tests: the E2E specs that reach the side panel go back to
a plain row click, which restores `clickRow`/`clickFirstRow`, and the inline
spec now seeds `rowClickAction: 'expand'` before navigating since the row hover
button and the row body's expand target only exist in that mode.

Co-authored-by: Mike Shi <mike@hyperdx.io>
@cursor
cursor Bot force-pushed the cursor/expand-log-inline-on-row-click-d7eb branch from 1959b18 to 8bafede Compare September 21, 2026 06:49
A row and its inline expansion are two `tr`s sharing one virtual index, and
both carried `data-index` and the virtualizer's `measureElement` ref. Since
virtual-core keys its element cache by index, the expanded `tr` took over that
index's ResizeObserver registration and cached its own height there. Collapsing
called the ref with `null`, which `measureElement` ignores, so the inflated
height stuck and nothing observed the row any more.

Each expand/collapse then shrank the render window until scrolling showed a few
rows above a screen of empty space. Wrap the pair in a `tbody` and measure that
instead, so one element maps to one virtual index.

Co-authored-by: Mike Shi <mike@hyperdx.io>
…and-log-inline-on-row-click-d7eb

# Conflicts:
#	packages/app/src/components/DBRowTable.tsx
#	packages/app/src/components/ExpandableRowTable.tsx
#	packages/app/tests/e2e/components/TableComponent.ts

Co-authored-by: Mike Shi <mike@hyperdx.io>
@github-actions github-actions Bot added review/tier-3 Standard — full human review required and removed review/tier-2 Low risk — AI review + quick human skim labels Sep 21, 2026
// cached there after it unmounted.
<tbody
key={virtualRow.key}
data-index={virtualRow.index}

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.

P1 Virtual row selectors break

Moving data-index from each virtualized data row to its enclosing tbody breaks the dashboard and chart-editor E2E helpers, which still wait for tbody tr[data-index]. Those helpers can no longer find the data rows, so tests that read cells or interact with rows will time out. Update those locators alongside this DOM change.

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

@cursor
cursor Bot changed the base branch from main to cursor/fix-virtualized-row-height-d7eb September 21, 2026 20:02
<IconLink size={16} />
</DBRowTableIconButton>
{onOpenSidePanel && (
<DBRowTableIconButton

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.

🔵 minor — The new "Open in side panel" control is not keyboard reachable

DBRowTableIconButton defaults to tabIndex = -1 and renders component="div" (DBRowTableIconButton.tsx:23,53-56), and this call site doesn't override it. Under rowClickAction: 'expand' the row body no longer opens the side panel, so for a keyboard-only user the direct route to the panel disappears entirely — the only remaining path is Tab to the row body, Enter to expand inline, then Tab to the expanded row's maximize <button>. Pass tabIndex={0} on this one button (it's a primary action, unlike the sibling copy affordances).

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

onToggleWrap={() =>
setWrapLinesEnabled(!wrapLinesEnabled)
}
onOpenSidePanel={

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.

🔵 minor — Hover "Open in side panel" button stays visible when a row click already opens the panel

onOpenSidePanel is gated on prefersInlineExpand && canOpenSidePanel, which ignores highlightedLineId. With the expand preference and the panel already open, expandInlineOnRowClick is false (line 475), so a row click opens/moves the panel — and the hover button does the same thing, which is exactly the duplication the default mode avoids and that DBRowTable.test.tsx:342-346 asserts against. Gate it on expandInlineOnRowClick instead, or accept the duplication deliberately and drop that assertion's rationale.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

sourceId={sourceId}
onRowDetailsClick={onOpenSidebar}
highlightedLineId={rowId ?? undefined}
highlightedLineId={sidePanel ? (rowId ?? undefined) : undefined}

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.

🔵 minor — The new highlightedLineId derivation has no test; reverting it keeps every test green

This is the fix for the stale rowWhere/rowSource signal, and it is the only thing that returns row clicks to inline expansion after the panel closes — but the unit tests inject highlightedLineId into RawLogTable directly (DBRowTable.test.tsx:465), so changing this line back to highlightedLineId={rowId ?? undefined} still passes. Add a test that renders DBSqlRowTableWithSideBar with rowWhere set and rowSource pointing at a different source, and asserts no row is highlighted.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

// Once the side panel is open it stays the active surface, so clicks keep
// moving it from row to row instead of expanding rows behind it.
const expandInlineOnRowClick =
prefersInlineExpand && (!canOpenSidePanel || highlightedLineId == null);

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.

🔵 minor — !canOpenSidePanel || in expandInlineOnRowClick is subsumed by prefersInlineExpand

When canOpenSidePanel is false, prefersInlineExpand (line 468) is already true whenever showExpandButton is, so the second !canOpenSidePanel branch can never change the result. Reduce to prefersInlineExpand && highlightedLineId == null and keep the comment about the panel staying the active surface.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

/>
</SettingContainer>

<Divider label="Search results" labelPosition="left" mt="sm" />

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.

🔵 minor — Preference is scoped as "Search results" but applies to every row table in the app

rowClickAction is read in RawLogTable, which backs dashboard table tiles (DBDashboardPage.tsx:1390), the Kubernetes pod/node/namespace side panels (PodDetailsSidePanel.tsx:203, NodeDetailsSidePanel.tsx:194, NamespaceDetailsSidePanel.tsx:181), the service-dashboard slowest-events tile and the chart preview panel — all of which are short, fixed-height surfaces where a tall inline expansion is a worse fit than the panel. Either retitle the section to something accurate (e.g. "Result rows") or thread an opt-in prop from the search page so the preference only governs the full-height results table.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

kodiakhq Bot pushed a commit that referenced this pull request Sep 22, 2026
## Summary

Expanding and then collapsing rows in the search results table permanently shrinks the virtualizer's render window. After a few expand/collapse cycles, scrolling leaves a screen of empty space with only a handful of rows in it — sometimes none at all, while the result count still reads 127.

**Root cause.** A row and its inline expansion are two `<tr>`s but one virtual item. Both carried `data-index={virtualRow.index}` and the virtualizer's `measureElement` ref:

- `virtual-core` keys `measureElementCache` by `item.key`, which is the index. When the expanded `<tr>` mounts, `_measureElement` sees `prevNode !== node`, **unobserves the base row**, observes the expanded row, and caches the expanded row's height as the size of that index.
- Collapsing calls the ref with `null`, and `measureElement` returns early for `null`. The `ResizeObserver` does fire for the detached node, but that path takes the `!node.isConnected` branch, which unobserves and returns *before* resizing.

So the index keeps the expanded height forever, and nothing observes that row any more, so it never re-measures. Every expand/collapse inflates one more index.

**Fix.** Wrap each row and its expansion in their own `<tbody>` and measure that instead, so one DOM element maps to one virtual index. The `ResizeObserver` on the `<tbody>` picks up both the row and the expansion growing or going away, so collapsing restores the correct height on its own. No stylesheet targets this table's `tbody`, and multiple `tbody` elements are valid in a `table`.

This is not a new regression — it reproduces on `main` today via the chevron. The expand/collapse affordance just makes it easy to hit.

### Screenshots or video

Six expand/collapse cycles on rows 0–5, then scrolling to the same offset. Left is `main`, right is this branch:

![Side by side: on main the search results table is blank below the column headers, on this branch it is full of log rows](https://cursor.com/artifacts/c/art-6af8d313-4d48-4640-922b-b5db9797df60)

Measured on the scroll container during that run:

| | rows rendered | space below last row |
| :-- | :-- | :-- |
| baseline, nothing expanded | 55 | −711px |
| after 6 expand/collapse, before fix | 32 | **+941px** at `scrollTop` 1200 |
| after 6 expand/collapse, with fix | 55 | −686px at `scrollTop` 1200 |

Negative means the rendered rows overfill the viewport, which is what you want. The viewport is 555px tall, so a +941px gap means nothing at all is visible.

### Tests

`packages/app/tests/e2e/features/search/virtualized-row-heights.spec.ts` covers both halves: that collapsing restores the render window, and that scrolling afterwards keeps the viewport covered. Both fail on `main` (`Expected: 55, Received: 38`; `Expected: <= 0, Received: 86`) and pass here.

`make ci-lint` and `make ci-unit` are green. The full `search/` E2E directory was re-run: 84 passed, 3 flaky-then-passed, and 2 `search-filters.spec.ts` failures that reproduce identically with `packages/app/src` reverted to `origin/main`, so they are environmental rather than from this change.

### How to test on Vercel preview

**Preview routes:** /search

**Steps:**

1. Wait for the results table to render rows.
2. Click the chevron at the left of the first row to expand it, then click it again to collapse it.
3. Repeat step 2 on the next five rows.
4. Scroll the results table down roughly one screen.
5. Verify the table is still full of log rows, with no blank area between the last row and the bottom of the table.

### References

- Linear Issue:
- Related PRs: #3117 adds an opt-in "expand inline on row click" preference, which makes this bug far easier to trigger. That PR is stacked on this one.



Linear Issue: [HDX-5365](https://linear.app/clickhouse/issue/HDX-5365/expand-log-inline-on-row-click)

<div><a href="https://cursor.com/agents/bc-b2c91bdd-e466-447c-942d-dce15159d7eb?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-b2c91bdd-e466-447c-942d-dce15159d7eb&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>



Co-authored-by: Cursor Agent <199161495+cursoragent@users.noreply.github.com>
@pulpdrew
pulpdrew changed the base branch from cursor/fix-virtualized-row-height-d7eb to main September 29, 2026 15:49
@kodiakhq kodiakhq Bot removed the automerge label Sep 29, 2026
@kodiakhq

kodiakhq Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

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

…ine-on-row-click-d7eb

# Conflicts:
#	packages/app/src/components/DBRowTable.tsx
#	packages/app/src/components/ExpandableRowTable.tsx
#	packages/app/tests/e2e/components/TableComponent.ts
'position-absolute top-0 end-0 mt-1 me-1 p-1 border-0 bg-transparent text-muted rounded',
styles.expandButton,
)}
onClick={() => openSidebar(rowId, source?.id)}

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.

🔵 minor — Expanded row's 'Open in side panel' skips onOpenSidebar and drops aliasWith, unlike the new hover button

The expanded row's button still opens the panel through useSidebarOpener, which only writes the rowWhere/rowSource URL params. The new hover button in DBRowTable goes through _onRowExpandClick → onOpenSidebar (DBSqlRowTableWithSidebar.tsx:93), which also sets aliasWith and fires onSidebarOpen. Opening a row from its expanded view therefore leaves aliasWith stale or empty, and with 'Expand inline' on this button is a main route to the panel. Fix: replace the canOpenSidePanel boolean with an onOpenSidePanel callback, pass () => _onRowExpandClick(row.original) as the hover button already does, and delete useSidebarOpener.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@github-actions github-actions Bot added review/tier-2 Low risk — AI review + quick human skim and removed review/tier-3 Standard — full human review required labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Duplicate code analysis

No new duplicate code introduced by this PR.


jscpd compared this branch against origin/main
across app, api and common-utils. Only new clones are listed; the codebase
currently holds 128 in total (1.82%).
Run yarn dupes locally to reproduce, or yarn dupes:all for the full picture.

'position-absolute top-0 end-0 mt-1 me-1 p-1 border-0 bg-transparent text-muted rounded',
styles.expandButton,
)}
onClick={() => openSidebar(rowId, source?.id)}

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.

🟠 major — The expanded row's 'Open in side panel' button skips aliasWith and live-tail pause that the hover button applies

With rowClickAction: 'expand', the PR offers two ways to open the side panel, and they behave differently. The hover button calls _onRowExpandClick, which reaches onOpenSidebar in DBSqlRowTableWithSidebar.tsx:93. That sets aliasWith and calls onSidebarOpen, which pauses live tail in DBSearchPage.tsx:1495. The maximize button in the expanded row goes through useSidebarOpener instead and only writes rowWhere/rowSource. So the panel opens with whatever aliasWith was left from the last open, and live tail keeps running. Fix: replace the new canOpenSidePanel?: boolean prop with onOpenSidePanel?: () => void, passed from DBRowTable.tsx:1319 as canOpenSidePanel ? () => _onRowExpandClick(row.original) : undefined, and delete useSidebarOpener. That leaves one code path for opening the panel.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

* returned as-is rather than merged with `DEFAULT_PREFERENCES`, so readers must
* fall back to `DEFAULT_ROW_CLICK_ACTION`.
*/
rowClickAction?: RowClickAction;

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.

🔵 minor — The rowClickAction default is repeated at every reader instead of applied once when preferences load

rowClickAction is optional, so each reader has to add ?? DEFAULT_ROW_CLICK_ACTION itself (DBRowTable.tsx:471, UserPreferencesModal.tsx:58). Any new reader that forgets it gets undefined. Fix: add rowClickAction: 'sidePanel' to DEFAULT_PREFERENCES and return { ...DEFAULT_PREFERENCES, ...parsed } from the already-migrated branch of migrateUserPreferences (line 151). Then the field can be required and both fallbacks go away.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

sourceId={sourceId}
onRowDetailsClick={onOpenSidebar}
highlightedLineId={rowId ?? undefined}
highlightedLineId={sidePanel ? (rowId ?? undefined) : undefined}

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.

🔵 minor — No test covers the new rule that hides highlightedLineId when the side panel isn't mounted

Inline expansion is disabled whenever highlightedLineId is set. That value now depends on the sidePanel element, which is null when rowSource points to another source. No test pins this, so dropping the ternary would pass CI while a stale rowWhere silently turns off inline expansion. Add a test for DBSqlRowTableWithSideBar that sets rowWhere plus a rowSource for a different source, then checks that a row click under expand expands inline instead of calling the side-panel opener.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@pulpdrew pulpdrew left a comment

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.

LGTM

@kodiakhq
kodiakhq Bot merged commit f800b09 into main Sep 29, 2026
29 checks passed
@kodiakhq
kodiakhq Bot deleted the cursor/expand-log-inline-on-row-click-d7eb branch September 29, 2026 17:10

This branch was successfully deployed

2 active deployments
Preview – hyperdx-oss — 466bca71 Deployed Sep 29, 2026 by vercel[bot]
Preview – hyperdx-storybook — 466bca71 Deployed Sep 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge review/tier-2 Low risk — AI review + quick human skim

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants