Outcome
The ES|QL Query Sandbox uses the in-editor toolbar layout from design: Search and the time controls sit inside the bordered editor panel above the Monaco area, the editor and results are split by an EuiResizableContainer, and the whole sandbox fills the flyout height instead of floating above the footer.
This is layout/styling only. It is split out from the AlertingDateRangePicker extraction (elastic/rna-program#527), which ships separately.
Open decision (blocking)
Where does the split-query control live? Today it is passed into the sandbox via the headerActions prop and renders as a "Split base and alert" button at the end of the in-editor toolbar. The design mock shows a settings gear outside the editor instead.
- Option A — keep it in the in-editor toolbar (current behavior, no change)
- Option B — move it into a settings menu/popover anchored outside the editor panel, which likely retires or repurposes the
headerActions slot
This is the next iteration of split query. Everything else below is independent of this choice.
Needs UX sign-off
Switching from the expanding editor to EuiResizableContainer changes how scrolling works:
- Before — the editor grew (native resize grip, 200px default up to 600px max) and the whole sandbox scrolled as one surface inside the flyout body.
- Now — the flyout body itself does not scroll (
.euiFlyoutBody__overflow is overflow: hidden so the layout can fill the flyout). The editor pane and the results pane each scroll independently, and the divider redistributes space between them.
Believed acceptable since both regions stay reachable, but it should be an explicit design call rather than an implementation side effect.
Decisions made
In-editor toolbar
Controls move from a row above the editor to inside the bordered editor panel, directly above Monaco. Order, left to right:
- Search (primary action, leftmost)
- Time field select
- Date range picker (
AlertingDateRangePicker, width="auto")
headerActions slot (currently split query — see open decision)
The toolbar row is pinned with flex-grow: 0 / flex-shrink: 0 so dragging the resize handle only changes the Monaco viewport, never the control row. The row uses wrap, so on narrow flyouts controls wrap to a second line and the row grows by content — intentional, and distinct from resize behavior.
CPS picker and the split-query tab bar stay outside the editor panel, above it.
Resizable container
Replace the native CSS resize: vertical shell (a raw div with resize / min-height / max-height) with a vertical EuiResizableContainer:
- Top panel — bordered editor panel,
initialSize={40}, minSize = MIN_EDITOR_HEIGHT
EuiResizableButton between the panels
- Bottom panel — results (validation errors, result count, empty/loading/error states, chart, data grid),
initialSize={60}, minSize="20%", scrolls internally
The browser-native resize grip in the editor's bottom-right corner is removed (deleted, not hidden) in favor of the EUI drag handle.
Full-height sandbox
The container was initially a fixed 560px, which left the sandbox floating above the flyout footer. It now fills the flyout, following the pattern already used by the yaml editor in compose_discover_flyout.tsx:
EuiFlyoutBody gets euiFullHeight() plus .euiFlyoutBody__overflow / .euiFlyoutBody__overflowContent overrides so it becomes a flex column with min-height: 0
- Sandbox root is a flex column filling that body
- Help text, CPS picker, and tabs form a fixed-size header block
EuiResizableContainer takes the remaining height
Padding
- Editor panel —
paddingSize="s" to paddingSize="m", per UX request for a wider inset around the toolbar (especially top and left). Asymmetric padding would need theme-token CSS rather than the paddingSize prop.
- Results panel —
paddingSize="none" to paddingSize="s" so validation callouts, the chart, and the grid are not flush against the pane edges.
Split-query behavior (verified)
The base/alert/recovery split relies on an auto-scroll that had to survive the new layout. In compose_discover_tabs.tsx, switching to the alert or recovery tab calls blockEditorRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) so the editable block scrolls past the locked base query.
Manually confirmed working in a POC. This holds because the immediate scroll parent (the editor body div) keeps overflow: auto through the rework, and the onAlertEditorMount / onRecoveryEditorMount completion handlers are untouched. The related concern that SPLIT_EDITOR_CONTAINER_STYLES (height: 100%) might fail to resolve against a now flex-derived parent — collapsing the block wrapper to MIN_EDITOR_HEIGHT — did not materialize.
Worth re-checking if the layout changes again: the flyout body is no longer a fallback scroller, so the editor pane is the only scroll surface for this interaction.
Minor, unrelated to scroll: EuiResizableButton takes focus on click, so grabbing the divider blurs Monaco. kbn-resizable-layout carries an explicit workaround for the same behavior if it proves annoying.
Acceptance Criteria
Implementation notes
Touched files, all in @kbn/alerting-v2-rule-form:
Cleanup to fold in before the PR:
- Layout is currently expressed as inline
React.CSSProperties objects. Repo convention is Emotion (@emotion/react) with EUI theme tokens; convert these and replace hardcoded values with tokens where one exists.
CpsPicker returns an EuiFlexItem but is now rendered directly inside a plain div, so it is an orphan flex item outside any EuiFlexGroup. Either wrap it or change what the component returns.
- Existing 23 sandbox tests pass, but only a label was updated. Add assertions for
querySandboxResizableContainer, querySandboxEditorPanel, and querySandboxResultsPanel.
- Confirm the default 40/60 pane split feels right with a short query; a two-line query currently leaves visible empty space under the code because Monaco fills the whole editor pane.
Related
Outcome
The ES|QL Query Sandbox uses the in-editor toolbar layout from design: Search and the time controls sit inside the bordered editor panel above the Monaco area, the editor and results are split by an
EuiResizableContainer, and the whole sandbox fills the flyout height instead of floating above the footer.This is layout/styling only. It is split out from the
AlertingDateRangePickerextraction (elastic/rna-program#527), which ships separately.Open decision (blocking)
Where does the split-query control live? Today it is passed into the sandbox via the
headerActionsprop and renders as a "Split base and alert" button at the end of the in-editor toolbar. The design mock shows a settings gear outside the editor instead.headerActionsslotThis is the next iteration of split query. Everything else below is independent of this choice.
Needs UX sign-off
Switching from the expanding editor to
EuiResizableContainerchanges how scrolling works:.euiFlyoutBody__overflowisoverflow: hiddenso the layout can fill the flyout). The editor pane and the results pane each scroll independently, and the divider redistributes space between them.Believed acceptable since both regions stay reachable, but it should be an explicit design call rather than an implementation side effect.
Decisions made
In-editor toolbar
Controls move from a row above the editor to inside the bordered editor panel, directly above Monaco. Order, left to right:
AlertingDateRangePicker,width="auto")headerActionsslot (currently split query — see open decision)The toolbar row is pinned with
flex-grow: 0/flex-shrink: 0so dragging the resize handle only changes the Monaco viewport, never the control row. The row useswrap, so on narrow flyouts controls wrap to a second line and the row grows by content — intentional, and distinct from resize behavior.CPS picker and the split-query tab bar stay outside the editor panel, above it.
Resizable container
Replace the native CSS
resize: verticalshell (a rawdivwithresize/min-height/max-height) with a verticalEuiResizableContainer:initialSize={40},minSize=MIN_EDITOR_HEIGHTEuiResizableButtonbetween the panelsinitialSize={60},minSize="20%", scrolls internallyThe browser-native resize grip in the editor's bottom-right corner is removed (deleted, not hidden) in favor of the EUI drag handle.
Full-height sandbox
The container was initially a fixed
560px, which left the sandbox floating above the flyout footer. It now fills the flyout, following the pattern already used by the yaml editor incompose_discover_flyout.tsx:EuiFlyoutBodygetseuiFullHeight()plus.euiFlyoutBody__overflow/.euiFlyoutBody__overflowContentoverrides so it becomes a flex column withmin-height: 0EuiResizableContainertakes the remaining heightPadding
paddingSize="s"topaddingSize="m", per UX request for a wider inset around the toolbar (especially top and left). Asymmetric padding would need theme-token CSS rather than thepaddingSizeprop.paddingSize="none"topaddingSize="s"so validation callouts, the chart, and the grid are not flush against the pane edges.Split-query behavior (verified)
The base/alert/recovery split relies on an auto-scroll that had to survive the new layout. In
compose_discover_tabs.tsx, switching to the alert or recovery tab callsblockEditorRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' })so the editable block scrolls past the locked base query.Manually confirmed working in a POC. This holds because the immediate scroll parent (the editor body div) keeps
overflow: autothrough the rework, and theonAlertEditorMount/onRecoveryEditorMountcompletion handlers are untouched. The related concern thatSPLIT_EDITOR_CONTAINER_STYLES(height: 100%) might fail to resolve against a now flex-derived parent — collapsing the block wrapper toMIN_EDITOR_HEIGHT— did not materialize.Worth re-checking if the layout changes again: the flyout body is no longer a fallback scroller, so the editor pane is the only scroll surface for this interaction.
Minor, unrelated to scroll:
EuiResizableButtontakes focus on click, so grabbing the divider blurs Monaco.kbn-resizable-layoutcarries an explicit workaround for the same behavior if it proves annoying.Acceptance Criteria
EuiResizableContainer; the native CSS resize grip is gonepaddingSize="m"; results panel usespaddingSize="s"Implementation notes
Touched files, all in
@kbn/alerting-v2-rule-form:query_sandbox.tsxquery_sandbox_flyout.tsxquery_sandbox.test.tsxCleanup to fold in before the PR:
React.CSSPropertiesobjects. Repo convention is Emotion (@emotion/react) with EUI theme tokens; convert these and replace hardcoded values with tokens where one exists.CpsPickerreturns anEuiFlexItembut is now rendered directly inside a plaindiv, so it is an orphan flex item outside anyEuiFlexGroup. Either wrap it or change what the component returns.querySandboxResizableContainer,querySandboxEditorPanel, andquerySandboxResultsPanel.Related