Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
97b08c1
fix: keep a multi-line search query visible when the search bar is bl…
elizabetdev Sep 15, 2026
b3c436a
fix: top-align search toolbar controls as the query bar grows
elizabetdev Sep 15, 2026
23991d1
fix: drop the WHERE label from the search bar
elizabetdev Sep 15, 2026
c1ab5ef
fix: match the Lucene input's height to the switch beside it
elizabetdev Sep 15, 2026
8747452
fix: hide the search bar's / shortcut overlay
elizabetdev Sep 15, 2026
111a4da
fix: drop an unnecessary cast in the seam check
elizabetdev Sep 15, 2026
6196b1b
fix: keep multiline SQL fields expanded
elizabetdev Sep 15, 2026
153905d
fix: honor allowMultiline for Lucene and review nits
elizabetdev Sep 15, 2026
b0a1984
fix: use complex :not() notation to satisfy stylelint
elizabetdev Sep 15, 2026
7ff897b
fix: give the query fields a working focus state
elizabetdev Sep 16, 2026
f8e682a
Merge branch 'main' into elizabet/hdx-5307-multiline-always-visible-s…
elizabetdev Sep 17, 2026
3ee2a29
fix: address multiline editor review feedback (#3132)
elizabetdev Sep 17, 2026
9d17df7
docs: document keepMultilineVisible in the search-bar changeset
elizabetdev Sep 17, 2026
129ce68
fix(app): keep inline editor width when the focus overlay opens
elizabetdev Sep 17, 2026
a8bb1c6
refactor(app): drop the expand-on-focus overlay
elizabetdev Sep 17, 2026
010a925
style: restore trailing newline in SQLInlineEditor.module.scss
elizabetdev Sep 17, 2026
e054676
fix(app): address multiline editor review feedback (#3132)
elizabetdev Sep 17, 2026
87af1ce
Merge remote-tracking branch 'origin/main' into elizabet/hdx-5307-mul…
elizabetdev Sep 17, 2026
0739c26
fix(app): keep single-line Lucene filters stable (#3132)
elizabetdev Sep 17, 2026
5d938d5
fix(app): scroll single-line Lucene filters horizontally
elizabetdev Sep 17, 2026
cbb3de2
fix(app): keep PageHeader block padding opt-in (#3132)
elizabetdev Sep 18, 2026
9bd39ba
Merge branch 'main' into elizabet/hdx-5307-multiline-always-visible-s…
elizabetdev Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/multiline-search-bar-always-visible.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@hyperdx/app': patch
---

fix: keep a multi-line query visible when the field is not focused

Multi-line SQL fields collapsed to a single line whenever they lost focus, so everything past the first line was hidden until you clicked back into them. Focusing one expanded it into a floating overlay that covered the content underneath — and in a container sized to its content, that overlay left the layout flow and shrank the field to a sliver one character wide.

Any field that allows multiple lines now simply sizes to its content, focused or not, growing the layout rather than floating over it, up to 150px before it scrolls. That covers the search WHERE, SELECT and ORDER BY, the chart editor's SQL fields, and PromQL. Lucene search fields similarly grow up to four lines. Fields with `allowMultiline={false}` remain single-line in both SQL and Lucene. The focus overlay is gone rather than made optional, so there is no longer a separate expand-on-focus state to reason about.

The language switch beside the search bar stretches to match, with no divider between it and the input. A Lucene bar at the default size also no longer overhangs that switch. The Lucene input reserved a 38px row while drawing a 36px box inside it, so the switch — sized to the 36px the SQL editor uses — stood 2px proud of the input. Both languages now take their height from one shared table, so the seam is flush and the bar does not change height when you switch. Only the default size was affected; the compact bar was already consistent at 30px.

Toolbar controls beside a growing search bar stay aligned with its first line (date picker, live tail, Run, and the Kubernetes filter row). Focus now recolors the whole control, including the language switch, without overriding an error or warning border.

The bar also drops its `WHERE` label. It repeated what the placeholder already says — "SQL WHERE clause (ex. column = 'foo')" — while taking horizontal room the query itself can use, which matters more now that a long query wraps rather than being cut off. Most places that embed this input already hid the label and labelled it themselves from the outside, so the option to show it is gone rather than merely defaulted off.

The `/` keycap that sat inside the bar is gone too. `/` and `s` still focus the input; only the overlay is removed so a long query is not clipped by a hint that is already documented in the keyboard shortcuts modal.
2 changes: 1 addition & 1 deletion .claude/skills/page-layout/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ import { PageLayout } from '@/components/PageLayout';
| List page | `AlertsPage.tsx` | `PageHeader` + `title` + `Container` (no inputs in header) |
| Tool page with inputs + hierarchy | `KubernetesDashboardPage.tsx`, `ClickhousePage.tsx` | `PageLayout` **without** `title`; **`breadcrumbs`** + `leading` + `actions` in one sticky header |
| Tool page (top-level) | `DBServiceMapPage.tsx` | `PageLayout` **without** `title`; `leading` / `actions` only; no duplicate breadcrumb unless you add a real hierarchy |
| Custom toolbar | `SessionsPage.tsx` | `PageLayout` + `header` = custom `PageHeader` `children` (single-row inputs, no `title`) |
| Custom toolbar | `SessionsPage.tsx` | `PageLayout` + `header` = custom `PageHeader` `children` with `growing` (single-row inputs, no `title`) |
| Custom title | `TeamPage.tsx` | `PageHeader` with `children` only |
3 changes: 2 additions & 1 deletion agent_docs/page_layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ Both live next to `withAppNav` in `packages/app/src/layout.tsx`, which wraps pag
| `leading` | Left cluster: source picker, badges, or other controls. When inputs are present, **do not** pair with `title` for the same page name. |
| `actions` | Right-aligned cluster: time range, Run/Save, sampling, refresh. |
| `children` | Full custom header when slots are not enough. Do not combine with `title` / `leading` / `actions` / `breadcrumbs` unless you use the breadcrumbs-only branch. |
| `growing` | Opt in to block padding that only shows once the toolbar outgrows `min-height`. Sessions uses this for its multi-line search; title-only pages omit it. |

Header styling is defined in `PageHeader.module.scss`: sticky top, bottom border, horizontal padding `var(--mantine-spacing-sm)` (same as Search `px="sm"`). Single-row headers keep `min-height: 60px`; stacked header (breadcrumbs + toolbar) grows with content.
Header styling is defined in `PageHeader.module.scss`: sticky top, bottom border, horizontal padding `var(--mantine-spacing-sm)` (same as Search `px="sm"`). Single-row headers keep `min-height: 60px`; stacked header (breadcrumbs + toolbar) grows with content. Pass `growing` when the toolbar can outgrow that min-height (Sessions' multi-line search) so block padding appears only once the row is taller than the bar.

## PageLayout API

Expand Down
1 change: 0 additions & 1 deletion packages/app/src/DBDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3117,7 +3117,6 @@ function DBDashboardPage({
onLanguageChange={(lang: 'sql' | 'lucene') =>
setValue('whereLanguage', lang)
}
label="WHERE"
enableHotkey
allowMultiline
minWidth={300}
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/DBSearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2369,7 +2369,8 @@ export function DBSearchPage() {
<Flex
gap="sm"
style={{ flex: '0 1 500px', minWidth: 0 }}
align="center"
// Stays level with the search bar's first line as it grows
align="flex-start"
>
<TimePicker
data-testid="time-picker"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,14 @@ function ServicesDashboardPage() {
}}
>
<Group gap="xs">
<Group justify="space-between" gap="xs" wrap="nowrap" flex={1}>
<Group
justify="space-between"
gap="xs"
wrap="nowrap"
flex={1}
// Stays level with the search bar's first line as it grows
align="flex-start"

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 — align="flex-start" top-aligns the 34px ActionIcons against 36px toolbar controls

Mantine Group defaults to align="center", which was centering the two size="lg" ActionIcons (34px, lines 385 and 400) against the 36px SearchWhereInput/TimePicker/Run button. With flex-start they now sit flush to the top and 2px short at the bottom — the same 2px seam this PR removes elsewhere. KubernetesFilters.tsx:325 has the same shape (28px FilterLinkToggle ActionIcon vs 30px size="xs" selects). Wrap the shorter controls the way ChartActionBar.tsx:160 already does in this diff: <Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center">…</Flex> (.xs for the Kubernetes row).

>
<SourceSelectControlled
control={control}
name="source"
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/SessionSubpanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,6 @@ export default function SessionSubpanel({
control={control}
name="where"
size="xs"
showLabel={false}
enableHotkey
onSubmit={handleSubmit(handleWhereSubmit)}
onLanguageChange={onLanguageChange}
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/SessionsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -434,12 +434,14 @@ function SessionsPage() {
<PageLayout
data-testid="sessions-page"
header={
<PageHeader>
<PageHeader growing>
<Group
justify="space-between"
gap="xs"
wrap="nowrap"
w="100%"
// Stays level with the search bar's first line as it grows
align="flex-start"
Comment thread
elizabetdev marked this conversation as resolved.
Resolved
className={styles.toolbar}
>
<SourceSelectControlled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '@tabler/icons-react';

import { ChartEditorFormState } from '@/components/ChartEditor/types';
import { EDITOR_INPUT_HEIGHTS } from '@/components/editorInputHeights';
import { SQLInlineEditorControlled } from '@/components/SQLEditor/SQLInlineEditor';
import { TimePicker } from '@/components/TimePicker';
import { IS_LOCAL_MODE } from '@/config';
Expand Down Expand Up @@ -154,9 +155,11 @@ export function ChartActionBar({
</Button>
)}
</Flex>
<Flex gap="sm" mb="sm" align="center" justify="end">
<Flex gap="sm" mb="sm" align="flex-start" justify="end">
Comment thread
elizabetdev marked this conversation as resolved.
Resolved
{filtersToggle != null && tabQueriesData(activeTab) && (
<DashboardFiltersToggle {...filtersToggle} />
<Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center">

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 editor's base height is re-asserted by hand at nine call sites

<Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center"> is repeated verbatim nine times across six files (here, ChartEditorControls.tsx:336/360, ChartSeriesEditor.tsx:525/550/576, HighlightedAttributes.tsx:89/98, MaterializedViews.tsx:576, OrderByFormRow.tsx:92) to keep a label or icon level with the neighbouring editor's first line. All nine hardcode .sm while the editors next to them infer their height from size, so the two must be kept in sync by hand: adding size="xs" to the ORDER BY editor below this line would silently leave the filters toggle 6px low, with nothing to catch it. Extract one small wrapper (taking the same size the editor takes, or a class keyed off the --editor-base-height the editor already publishes) and use it at all nine sites.

<DashboardFiltersToggle {...filtersToggle} />
</Flex>
)}
{(activeTab === 'table' ||
activeTab === 'pie' ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
isFormulaDisplayType,
isFormulaSourceKind,
} from '@/components/ChartEditor/utils';
import { EDITOR_INPUT_HEIGHTS } from '@/components/editorInputHeights';
import MVOptimizationIndicator from '@/components/MaterializedViews/MVOptimizationIndicator';
import SearchWhereInput from '@/components/SearchInput/SearchWhereInput';
import SourceSchemaPreview, {
Expand Down Expand Up @@ -269,7 +270,6 @@ export function ChartEditorControls({
onLanguageChange={(lang: 'sql' | 'lucene') =>
setValue('whereLanguage', lang)
}
showLabel={false}
enableVariables
/>
</Flex>
Expand Down Expand Up @@ -325,13 +325,15 @@ export function ChartEditorControls({
<>
<Divider mt="md" mb="sm" />
<div
className="gap-2 align-items-center"
className="gap-2"
style={{
display: 'grid',
gridTemplateColumns: 'auto minmax(0, 1fr)',
// Labels stay level with each editor's first line as it grows
alignItems: 'start',
Comment thread
elizabetdev marked this conversation as resolved.
Resolved
}}
>
<div>
<Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center">
<Text
me="sm"
size="sm"
Expand All @@ -341,7 +343,7 @@ export function ChartEditorControls({
>
Group By
</Text>
</div>
</Flex>
<div>
<SQLInlineEditorControlled
{...groupByConnectionProps}
Expand All @@ -355,7 +357,7 @@ export function ChartEditorControls({
</div>
{displayType === DisplayType.Table && (
<>
<div>
<Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center">
<Text
me="sm"
size="sm"
Expand All @@ -365,7 +367,7 @@ export function ChartEditorControls({
>
Having
</Text>
</div>
</Flex>
<div>
<SQLInlineEditorControlled
tableConnection={tableConnection}
Expand Down Expand Up @@ -560,7 +562,6 @@ export function ChartEditorControls({
onLanguageChange={(lang: 'sql' | 'lucene') =>
setValue('whereLanguage', lang)
}
showLabel={false}
enableVariables
/>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
SavedChartConfigWithSelectArray,
} from '@/components/ChartEditor/types';
import { isFormulaSourceKind } from '@/components/ChartEditor/utils';
import { EDITOR_INPUT_HEIGHTS } from '@/components/editorInputHeights';
import {
CheckBoxControlled,
TextInputControlled,
Expand Down Expand Up @@ -511,15 +512,19 @@ export function ChartSeriesEditor({
)}
{(showWhere || showGroupBy || showHaving) && (
<div
className="flex-grow-1 gap-2 align-items-center"
className="flex-grow-1 gap-2"
style={{
display: 'grid',
gridTemplateColumns: 'auto 1fr auto 1fr',
// Labels stay level with each editor's first line as it grows
alignItems: 'start',
}}
>
{showWhere && (
<>
<Text size="sm">Where</Text>
<Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center">

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 — <Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center"> is copy-pasted ten times across six files

The same "centre this control on the editor's first line" wrapper is now hand-rolled at ChartSeriesEditor.tsx:525,550,576, ChartEditorControls.tsx:336,360, ChartActionBar.tsx:160, HighlightedAttributes.tsx:89,98, MaterializedViews.tsx:576 and OrderByFormRow.tsx:92, each hard-coding the sm variant. Every one of them silently desyncs if the editor beside it is ever given size="xs" (30px), and the repo conventions make grepping for an existing component before adding a new one REQUIRED. Extract one small component next to editorInputHeights.ts (e.g. <EditorFirstLineSlot size="sm">{children}</EditorFirstLineSlot>, or a shared class keyed off --editor-base-height so it inherits the height the editor already publishes) and use it at all ten sites.

<Text size="sm">Where</Text>
</Flex>
<div
style={{
gridColumn:
Expand All @@ -533,7 +538,6 @@ export function ChartSeriesEditor({
control={control}
name={`${namePrefix}aggCondition`}
onSubmit={onSubmit}
showLabel={false}
additionalSuggestions={attributeSuggestions}
data-testid="series-where-input"
enableVariables
Expand All @@ -543,9 +547,11 @@ export function ChartSeriesEditor({
)}
{showGroupBy && (
<>
<Text size="sm" style={{ whiteSpace: 'nowrap' }}>
Group By
</Text>
<Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center">
<Text size="sm" style={{ whiteSpace: 'nowrap' }}>
Group By
</Text>
</Flex>
<div
style={{
minWidth: 200,
Expand All @@ -567,9 +573,11 @@ export function ChartSeriesEditor({
</div>
{showHaving && (
<>
<Text size="sm" style={{ whiteSpace: 'nowrap' }}>
Having
</Text>
<Flex h={EDITOR_INPUT_HEIGHTS.sm} align="center">
<Text size="sm" style={{ whiteSpace: 'nowrap' }}>
Having
</Text>
</Flex>
<div style={{ minWidth: 300, maxWidth: '100%' }}>
<SQLInlineEditorControlled
tableConnection={tableConnection}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function HeatmapSeriesEditor({
onLanguageChange={(lang: 'sql' | 'lucene') =>
setValue('whereLanguage', lang)
}
showLabel={false}
enableVariables
/>
<Divider />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ function SearchOnClickFields({ control }: { control: DrawerControl }) {
name="onClick.whereTemplate"
languageName="onClick.whereLanguage"
allowMultiline
showLabel={false}
sqlPlaceholder="ServiceName = '{{ServiceName}}'"
lucenePlaceholder="ServiceName:{{ServiceName}}"
/>
Expand Down Expand Up @@ -162,7 +161,6 @@ function DashboardOnClickFields({
name="onClick.whereTemplate"
languageName="onClick.whereLanguage"
allowMultiline
showLabel={false}
sqlPlaceholder="ServiceName = '{{ServiceName}}'"
lucenePlaceholder="ServiceName:{{ServiceName}}"
/>
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/components/DBTracePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,8 @@ export default function DBTracePanel({
{!traceId && parentSourceId != null && (
<Stack gap="xs" mb="sm">
<Text size="xs">Trace ID Expression</Text>
<Flex align="center">
{/* The submit button stays level with the editor's first line as it grows */}
<Flex align="flex-start">
<SQLInlineEditorControlled
tableConnection={tcFromSource(parentSourceData)}
name="traceIdExpression"
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/components/DBTraceWaterfallChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,6 @@ export function DBTraceWaterfallChartContainer({
languageName="traceWhereLanguage"
control={control}
size="xs"
showLabel={false}
allowMultiline={false}
onSubmit={handleSubmit(onSubmitFilters)}
onLanguageChange={lang =>
Expand Down Expand Up @@ -1327,7 +1326,6 @@ export function DBTraceWaterfallChartContainer({
languageName="logWhereLanguage"
control={control}
size="xs"
showLabel={false}
allowMultiline={false}
onSubmit={handleSubmit(onSubmitFilters)}
onLanguageChange={lang =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ export const QueryExpressionFilterEditForm = ({
control={control}
name="expression"
placeholder="SQL column or expression"
language="sql"
enableHotkey
rules={{ required: true }}
parentRef={TOOLTIP_PORTAL_TARGET}
Expand All @@ -198,7 +197,6 @@ export const QueryExpressionFilterEditForm = ({
control={control}
name="where"
languageName="whereLanguage"
showLabel={false}
allowMultiline={true}
sqlPlaceholder="Filter for dropdown values"
lucenePlaceholder="Filter for dropdown values"
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/components/HeatmapSettingsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export default function HeatmapSettingsDrawer({
size="xs"
tooltipText="Controls the Y axis range and scale — defines the metric plotted vertically."
placeholder="SQL expression"
language="sql"
onSubmit={form.handleSubmit(onSubmit)}
label="Value"
error={form.formState.errors.value?.message}
Expand All @@ -111,7 +110,6 @@ export default function HeatmapSettingsDrawer({
control={form.control}
name="count"
placeholder="SQL expression"
language="sql"
size="xs"
tooltipText="Controls the color intensity (Z axis) — shows how frequently or strongly each value occurs."
onSubmit={form.handleSubmit(onSubmit)}
Expand Down
9 changes: 8 additions & 1 deletion packages/app/src/components/KubernetesFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,14 @@ export const KubernetesFilters: React.FC<KubernetesFiltersProps> = ({
};

return (
<Group mt="md" mb="xs" wrap="wrap" gap="xxs">
<Group
mt="md"
mb="xs"
wrap="wrap"
gap="xxs"
// Stays level with the search bar's first line as it grows
align="flex-start"
>
{K8S_FILTER_FIELDS.map(({ field, placeholder, dataTestId }) => (
<FilterSelect
key={field}
Expand Down
10 changes: 9 additions & 1 deletion packages/app/src/components/PageHeader.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
justify-content: space-between;
line-height: 1;

/* Match Search page toolbar: DBSearchPage uses Mantine `px="sm"` */
/* Match Search page toolbar: DBSearchPage uses Mantine `px="sm"`. */
padding: 0 var(--mantine-spacing-sm);
position: sticky;
top: 0;
Expand All @@ -22,6 +22,14 @@
z-index: 2;
}

/* A toolbar that can grow past `min-height` (Sessions' multi-line search).
Block padding is absorbed at rest and only shows once the row outgrows the
header — otherwise the query sits flush against the edges. Other pages keep
the zero block padding above so a title-only bar does not shift. */
.header.growing {
padding-block: var(--mantine-spacing-xs);
}

/* Applied automatically when a `stickyRow` is provided: the chrome scrolls
away with the page and the `stickyRow` below becomes the only pinned bar.
The chrome's bottom border and bottom padding are also dropped — the
Expand Down
Loading
Loading