diff --git a/.changeset/multiline-search-bar-always-visible.md b/.changeset/multiline-search-bar-always-visible.md new file mode 100644 index 0000000000..99cb68ba0f --- /dev/null +++ b/.changeset/multiline-search-bar-always-visible.md @@ -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. diff --git a/.claude/skills/page-layout/SKILL.md b/.claude/skills/page-layout/SKILL.md index 7d2cadccd2..2a4577cc77 100644 --- a/.claude/skills/page-layout/SKILL.md +++ b/.claude/skills/page-layout/SKILL.md @@ -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 | diff --git a/agent_docs/page_layout.md b/agent_docs/page_layout.md index 18d7274bb2..1c34590d26 100644 --- a/agent_docs/page_layout.md +++ b/agent_docs/page_layout.md @@ -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 diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index da2338bfa8..81441a79f5 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -3117,7 +3117,6 @@ function DBDashboardPage({ onLanguageChange={(lang: 'sql' | 'lucene') => setValue('whereLanguage', lang) } - label="WHERE" enableHotkey allowMultiline minWidth={300} diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx index ee30e8c46d..8000a4c788 100644 --- a/packages/app/src/DBSearchPage.tsx +++ b/packages/app/src/DBSearchPage.tsx @@ -2369,7 +2369,8 @@ export function DBSearchPage() { - + + )} - + {filtersToggle != null && tabQueriesData(activeTab) && ( - + + + )} {(activeTab === 'table' || activeTab === 'pie' || diff --git a/packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx b/packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx index a172809faf..4443c6e503 100644 --- a/packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx +++ b/packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx @@ -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, { @@ -269,7 +270,6 @@ export function ChartEditorControls({ onLanguageChange={(lang: 'sql' | 'lucene') => setValue('whereLanguage', lang) } - showLabel={false} enableVariables /> @@ -325,13 +325,15 @@ export function ChartEditorControls({ <>
-
+ Group By -
+
{displayType === DisplayType.Table && ( <> -
+ Having -
+
setValue('whereLanguage', lang) } - showLabel={false} enableVariables /> diff --git a/packages/app/src/components/DBEditTimeChartForm/ChartSeriesEditor.tsx b/packages/app/src/components/DBEditTimeChartForm/ChartSeriesEditor.tsx index a87bb3efdc..d946d8340a 100644 --- a/packages/app/src/components/DBEditTimeChartForm/ChartSeriesEditor.tsx +++ b/packages/app/src/components/DBEditTimeChartForm/ChartSeriesEditor.tsx @@ -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, @@ -511,15 +512,19 @@ export function ChartSeriesEditor({ )} {(showWhere || showGroupBy || showHaving) && (
{showWhere && ( <> - Where + + Where +
- - Group By - + + + Group By + +
{showHaving && ( <> - - Having - + + + Having + +
setValue('whereLanguage', lang) } - showLabel={false} enableVariables /> diff --git a/packages/app/src/components/DBEditTimeChartForm/OnClickForm/OnClickDrawer.tsx b/packages/app/src/components/DBEditTimeChartForm/OnClickForm/OnClickDrawer.tsx index a5dcd2f1e7..2a5a339033 100644 --- a/packages/app/src/components/DBEditTimeChartForm/OnClickForm/OnClickDrawer.tsx +++ b/packages/app/src/components/DBEditTimeChartForm/OnClickForm/OnClickDrawer.tsx @@ -79,7 +79,6 @@ function SearchOnClickFields({ control }: { control: DrawerControl }) { name="onClick.whereTemplate" languageName="onClick.whereLanguage" allowMultiline - showLabel={false} sqlPlaceholder="ServiceName = '{{ServiceName}}'" lucenePlaceholder="ServiceName:{{ServiceName}}" /> @@ -162,7 +161,6 @@ function DashboardOnClickFields({ name="onClick.whereTemplate" languageName="onClick.whereLanguage" allowMultiline - showLabel={false} sqlPlaceholder="ServiceName = '{{ServiceName}}'" lucenePlaceholder="ServiceName:{{ServiceName}}" /> diff --git a/packages/app/src/components/DBTracePanel.tsx b/packages/app/src/components/DBTracePanel.tsx index 35a7ea8774..e23c2b7902 100644 --- a/packages/app/src/components/DBTracePanel.tsx +++ b/packages/app/src/components/DBTracePanel.tsx @@ -397,7 +397,8 @@ export default function DBTracePanel({ {!traceId && parentSourceId != null && ( Trace ID Expression - + {/* The submit button stays level with the editor's first line as it grows */} + @@ -1327,7 +1326,6 @@ export function DBTraceWaterfallChartContainer({ languageName="logWhereLanguage" control={control} size="xs" - showLabel={false} allowMultiline={false} onSubmit={handleSubmit(onSubmitFilters)} onLanguageChange={lang => diff --git a/packages/app/src/components/DashboardFiltersModal/QueryExpressionFilterEditForm.tsx b/packages/app/src/components/DashboardFiltersModal/QueryExpressionFilterEditForm.tsx index 0eeb04b47f..7c455f605c 100644 --- a/packages/app/src/components/DashboardFiltersModal/QueryExpressionFilterEditForm.tsx +++ b/packages/app/src/components/DashboardFiltersModal/QueryExpressionFilterEditForm.tsx @@ -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} @@ -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" diff --git a/packages/app/src/components/HeatmapSettingsDrawer.tsx b/packages/app/src/components/HeatmapSettingsDrawer.tsx index 8bc07138cf..975f1701f3 100644 --- a/packages/app/src/components/HeatmapSettingsDrawer.tsx +++ b/packages/app/src/components/HeatmapSettingsDrawer.tsx @@ -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} @@ -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)} diff --git a/packages/app/src/components/KubernetesFilters.tsx b/packages/app/src/components/KubernetesFilters.tsx index c6fb19082d..b5939d1d80 100644 --- a/packages/app/src/components/KubernetesFilters.tsx +++ b/packages/app/src/components/KubernetesFilters.tsx @@ -316,7 +316,14 @@ export const KubernetesFilters: React.FC = ({ }; return ( - + {K8S_FILTER_FIELDS.map(({ field, placeholder, dataTestId }) => ( `, which lets it stay pinned for the full // length of the page rather than only the height of the header. const chromeStickyClass = hasStickyRow ? styles.notSticky : undefined; + const chromeClassName = classNames( + styles.header, + growing && styles.growing, + chromeStickyClass, + className, + ); const toolbarInner = ( <> @@ -118,21 +131,13 @@ export function PageHeader({ // it. A page with only a `stickyRow` (no breadcrumbs, no title) // skips the empty chrome entirely. children != null ? ( -
+
{children}
) : null ) : hasBreadcrumbs && hasToolbar ? (
{breadcrumbs}
@@ -140,22 +145,14 @@ export function PageHeader({
) : hasBreadcrumbs && !hasToolbar ? (
{breadcrumbs}
{children}
) : ( -
+
{toolbarInner}
); diff --git a/packages/app/src/components/PromQLEditor/PromQLEditor.tsx b/packages/app/src/components/PromQLEditor/PromQLEditor.tsx index 453769e1fc..28cf3b98db 100644 --- a/packages/app/src/components/PromQLEditor/PromQLEditor.tsx +++ b/packages/app/src/components/PromQLEditor/PromQLEditor.tsx @@ -18,6 +18,7 @@ import CodeMirror, { tooltips, } from '@uiw/react-codemirror'; +import { EDITOR_INPUT_HEIGHTS } from '@/components/editorInputHeights'; import { createCodeMirrorStyleTheme, DEFAULT_CODE_MIRROR_BASIC_SETUP, @@ -197,36 +198,26 @@ export default function PromQLEditor({ } }, []); - const isExpanded = isFocused; const isVariableWarningOnly = variableIssues.errors.length === 0 && variableIssues.warnings.length > 0; - const baseHeight = 36; + const baseHeight = EDITOR_INPUT_HEIGHTS.sm; return (
- {isExpanded &&