Problem
The Dashboard header consumes too much vertical space on narrow screens.
The current sticky top area contains two wrapping rows:
- the Dashboard header;
- the Dashboard toolbar.
On mobile, the header can wrap the following items across several lines:
- back link and
SQL Browser text;
- Dashboard title;
- favorite count;
- skipped-tile note;
- hostname;
- updated timestamp;
- theme button;
- Refresh button and text.
The toolbar also remains visible with:
- the four-way layout switcher;
- all Dashboard filters;
- wrapping filter fields.
This leaves too little room for Dashboard content.
The issue is visible at typical phone widths where the sticky header and toolbar occupy a large portion of the viewport before the first panel begins.
Goal
At the application’s mobile breakpoint, render a compact Dashboard header and a single horizontally scrolling filter row.
The mobile Dashboard should show:
[back icon] [truncated Dashboard title] [theme icon] [refresh icon]
[filter] [filter] [filter] [filter] ...
The Dashboard content must begin immediately after those rows.
Mobile breakpoint
Use the application’s canonical mobile breakpoint:
Keep this aligned with the existing mobile workbench breakpoint.
Do not introduce another Dashboard-only mobile breakpoint for the same behavior.
Existing narrower Dashboard rules may remain only where they serve a distinct purpose; otherwise merge them into the 768 px rules.
Mobile header
At max-width: 768px, the Dashboard header must remain one line.
Visible controls
Keep:
- back navigation as an icon-only button/link;
- Dashboard title;
- theme toggle;
- Refresh button as an icon-only button.
Required visual order:
back
title
flexible spacer
theme
refresh
Hidden content
Hide on mobile:
SQL Browser text next to the back icon;
- favorite-count chip;
- hostname/source chip;
- updated timestamp;
- Refresh text;
- layout selector and its
Layout label.
The skipped-tile note may remain hidden with the other header metadata. Hidden/empty tiles remain represented by their normal Dashboard behavior; mobile does not need a persistent count in the header.
Dashboard title
The title must consume the remaining horizontal space without forcing a wrap:
.dash-title {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Long Library names must truncate with an ellipsis.
Header sizing
Recommended mobile style:
@media (max-width: 768px) {
.dash-header {
flex-wrap: nowrap;
gap: 8px;
padding: 8px 10px;
}
}
The exact pixel values may follow the existing compact mobile vocabulary, but the header must remain one line at representative widths down to 360 px.
Back navigation
On desktop, keep the current icon plus SQL Browser label.
On mobile:
- hide only the visible text;
- keep the link target unchanged;
- retain an accessible name such as:
aria-label="Back to SQL Browser"
- retain the existing tooltip/title.
Do not replace the back link with browser-history navigation.
Hostname
Do not display the ClickHouse hostname/source chip on mobile.
Desktop behavior remains unchanged.
The mobile rule must hide the entire source chip, including:
- status dot;
- hostname text;
- source tooltip trigger.
The connection remains implicit in the Dashboard context and does not justify mobile header space.
Refresh button
On mobile, render Refresh as an icon-only button.
Requirements:
- hide the visible
Refresh text;
- keep the refresh icon;
- preserve the existing click behavior;
- preserve disabled state during refresh;
- preserve the existing tooltip;
- add or retain an accessible name:
aria-label="Refresh dashboard"
The icon-only button should use the same compact dimensions as the theme button.
Desktop retains icon plus text.
Do not change refresh scheduling, cancellation, concurrency, or query behavior.
Favorite count and updated timestamp
Hide both on mobile:
N favorites
Updated HH:MM
Desktop behavior remains unchanged.
These are secondary status details and must not force the header to wrap.
Do not delete their runtime updates. They remain available when the viewport widens.
Layout selector
The Dashboard layout switcher is not needed on mobile.
Hide:
Layout label;
Full width;
Report;
2 columns;
3 columns.
Mobile visual layout
The Dashboard must visually use one normal full-width column at mobile widths, regardless of the saved desktop preference.
This includes overriding Report-mode presentation.
Required behavior:
@media (max-width: 768px) {
.dash-layout-wrap {
display: none;
}
.dash-grid,
.dash-grid.is-wide,
.dash-grid.is-report {
grid-template-columns: 1fr;
max-width: none;
width: 100%;
margin: 0;
}
.dash-grid.is-report .dash-tile {
min-height: 300px;
}
}
The exact normal tile minimum height should match the ordinary Dashboard tile rule.
Persisted preference
Do not modify:
state.dashLayout
state.dashCols
asb:dashLayout
asb:dashCols
Mobile is only a responsive visual override.
When the viewport widens again, the saved desktop layout must return automatically.
Changing orientation must not write new preferences.
Changing layout on desktop must still persist exactly as before.
Filter row
Dashboard filters must render in one non-wrapping horizontal row on mobile.
Current wrapping behavior causes the toolbar to grow vertically as more filters are added.
Use the same interaction pattern as the workbench variable strip:
@media (max-width: 768px) {
.dash-toolbar {
flex-wrap: nowrap;
padding: 6px 10px;
}
.dash-filter-host {
width: 100%;
min-width: 0;
overflow: hidden;
}
.dash-filters {
width: 100%;
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
overscroll-behavior-x: contain;
-webkit-overflow-scrolling: touch;
}
.dash-filters::-webkit-scrollbar {
display: none;
}
.dash-filters > .var-field {
flex-shrink: 0;
}
}
Requirements:
- fields remain at their normal readable width;
- fields do not shrink to fit;
- fields never wrap to another row;
- users can swipe horizontally;
- the next partially visible field may serve as the scroll cue;
- vertical Dashboard scrolling must remain natural;
- horizontal swiping over the filter strip must not move the complete page sideways.
Filter controls and popovers
All existing filter control kinds must continue to work in the horizontal strip:
- plain text and recent-value controls;
- relative-time controls;
- Enum controls;
- curated Filter-role option controls;
- optional activation controls;
- validation and type-conflict styling.
Dropdowns and footers already use fixed positioning to escape scrolling containers.
Verify that:
- combobox lists are not clipped by the filter strip;
- dropdown positioning remains anchored to the input;
- the footer aligns with the dropdown;
- selecting an item does not unexpectedly scroll the Dashboard;
- keyboard interaction remains available;
- invalid/conflict tooltips remain available.
Do not add another mobile-specific filter renderer.
Empty toolbar
When the Dashboard has no filters, hiding the mobile layout selector must not leave an empty sticky toolbar row.
The renderer already knows whether filter controls exist.
Add an explicit class or attribute:
const toolbar = h('div', {
class: 'dash-toolbar' + (controls.length ? ' has-filters' : ''),
}, layoutWrap, filterHost);
Then:
@media (max-width: 768px) {
.dash-toolbar:not(.has-filters) {
display: none;
}
}
Equivalent naming is acceptable.
Requirements:
- no empty padding;
- no empty border;
- no blank sticky row;
- desktop layout selector remains visible even when there are no filters.
Sticky behavior
Keep the compact mobile header and filter row sticky.
The top area may contain:
- one header row;
- one filter row when filters exist.
It must never contain:
- a wrapped header;
- a layout row;
- several wrapped filter rows;
- an empty toolbar.
The first Dashboard panel must remain reachable without excessive initial scrolling.
Do not make the complete Dashboard header non-sticky in this issue.
Desktop behavior
At widths above 768 px, preserve the current Dashboard behavior:
- full back-link text;
- Dashboard title;
- favorite count;
- skipped count;
- hostname chip;
- updated timestamp;
- theme toggle;
- Refresh icon and label;
- layout switcher;
- wrapping Dashboard filter bar;
- persisted layout application.
This issue is a responsive presentation change only.
Suggested class hooks
Add explicit hooks rather than relying on fragile :nth-child selectors.
Suggested classes:
.dash-back-label
.dash-refresh-label
.dash-header-meta
.dash-toolbar.has-filters
Existing classes may be reused where already precise:
.dash-fav
.dash-src
.dash-updated
.dash-layout-wrap
Example markup changes:
h('a', {
class: 'dash-back',
href: app.basePath || '/sql',
title: 'Back to SQL Browser',
'aria-label': 'Back to SQL Browser',
}, Icon.arrow(), h('span', { class: 'dash-back-label' }, 'SQL Browser'));
const refreshBtn = h('button', {
class: 'dash-btn dash-refresh',
title: 'Re-run all tiles',
'aria-label': 'Refresh dashboard',
}, Icon.refresh(), h('span', { class: 'dash-refresh-label' }, 'Refresh'));
Files
Expected modifications:
src/ui/dashboard.js
src/styles.css
Expected tests:
tests/unit/dashboard.test.js
tests/e2e/dashboard*.spec.js
Add or update a focused Dashboard fixture if required.
No schema change.
No saved-query migration.
No new runtime dependency.
Tests
Header at phone width
At a representative viewport such as:
verify:
.dash-header remains one row;
- back icon is visible;
SQL Browser text is hidden;
- Dashboard title is visible and truncated when needed;
- favorite-count chip is hidden;
- hostname chip is hidden;
- updated timestamp is hidden;
- theme button is visible;
- Refresh icon is visible;
- Refresh text is hidden;
- Refresh has an accessible name;
- no header item overflows the viewport.
Very narrow phone
At a width near:
verify:
- title shrinks before action buttons;
- theme and Refresh remain reachable;
- header does not wrap;
- no horizontal page overflow.
Layout override
Set each persisted desktop layout:
Full width
Report
2 columns
3 columns
At mobile width, verify:
- layout selector is hidden;
- Dashboard grid has one column;
- Report maximum width is removed;
- Report tall-tile minimum is removed;
- tiles fill the available content width;
- persisted preference values are unchanged.
Widen the viewport and verify the saved desktop layout returns.
Filter row
With several filter controls:
- the toolbar remains one row;
- filters do not wrap;
- every
.var-field retains its normal width;
- the filter strip has horizontal overflow;
- horizontal swipe/scroll reaches the final filter;
- Dashboard content remains vertically scrollable;
- no horizontal page overflow is introduced.
Filter variants
Cover:
- plain text;
- relative time;
- Enum;
- curated Filter-role control;
- optional filter;
- type-conflict state;
- invalid state.
Verify dropdowns remain visible outside the scroll strip.
No filters
With no Dashboard parameters:
- layout selector is hidden on mobile;
- toolbar is absent;
- no empty sticky row remains;
- grid begins immediately below the compact header.
On desktop, the layout selector remains visible.
Refresh state
During Refresh:
- icon-only button becomes disabled;
- loading state is still discernible;
- no text appears and changes header width;
- completion restores the enabled state;
- queries run exactly once under existing scheduling.
Landscape
At a mobile-landscape or narrow-tablet width near the breakpoint:
- behavior changes consistently at 768 px;
- no intermediate wrapped state appears;
- widening beyond the breakpoint restores desktop controls.
Accessibility
Verify:
- back link accessible name;
- Refresh button accessible name;
- theme button accessible name;
- hidden labels are not announced twice;
- source/favorite/updated hidden elements are not focusable;
- filter fields preserve their labels and keyboard behavior.
Non-goals
Do not include:
- changes to Dashboard query execution;
- changes to Refresh scheduling or concurrency;
- changes to persisted layout schema;
- deletion of desktop layout options;
- mobile-specific saved layout preferences;
- collapsing filters into a modal or drawer;
- a new filter renderer;
- hiding the Dashboard title;
- making the Dashboard header non-sticky;
- changes to panel content rendering;
- changes to KPI mobile card behavior;
- changes to workbench mobile navigation.
Acceptance criteria
Definition of done
On mobile, the Dashboard uses one compact sticky header row containing only back navigation, the truncated Dashboard title, theme, and Refresh icons.
The layout selector, favorite count, hostname, updated timestamp, and Refresh label are hidden. Dashboard panels use one full-width column without changing the saved desktop preference. Filters occupy one horizontally scrollable row, and Dashboards without filters have no empty toolbar.
Problem
The Dashboard header consumes too much vertical space on narrow screens.
The current sticky top area contains two wrapping rows:
On mobile, the header can wrap the following items across several lines:
SQL Browsertext;The toolbar also remains visible with:
This leaves too little room for Dashboard content.
The issue is visible at typical phone widths where the sticky header and toolbar occupy a large portion of the viewport before the first panel begins.
Goal
At the application’s mobile breakpoint, render a compact Dashboard header and a single horizontally scrolling filter row.
The mobile Dashboard should show:
The Dashboard content must begin immediately after those rows.
Mobile breakpoint
Use the application’s canonical mobile breakpoint:
Keep this aligned with the existing mobile workbench breakpoint.
Do not introduce another Dashboard-only mobile breakpoint for the same behavior.
Existing narrower Dashboard rules may remain only where they serve a distinct purpose; otherwise merge them into the 768 px rules.
Mobile header
At
max-width: 768px, the Dashboard header must remain one line.Visible controls
Keep:
Required visual order:
Hidden content
Hide on mobile:
SQL Browsertext next to the back icon;Layoutlabel.The skipped-tile note may remain hidden with the other header metadata. Hidden/empty tiles remain represented by their normal Dashboard behavior; mobile does not need a persistent count in the header.
Dashboard title
The title must consume the remaining horizontal space without forcing a wrap:
Long Library names must truncate with an ellipsis.
Header sizing
Recommended mobile style:
The exact pixel values may follow the existing compact mobile vocabulary, but the header must remain one line at representative widths down to 360 px.
Back navigation
On desktop, keep the current icon plus
SQL Browserlabel.On mobile:
Do not replace the back link with browser-history navigation.
Hostname
Do not display the ClickHouse hostname/source chip on mobile.
Desktop behavior remains unchanged.
The mobile rule must hide the entire source chip, including:
The connection remains implicit in the Dashboard context and does not justify mobile header space.
Refresh button
On mobile, render Refresh as an icon-only button.
Requirements:
Refreshtext;The icon-only button should use the same compact dimensions as the theme button.
Desktop retains icon plus text.
Do not change refresh scheduling, cancellation, concurrency, or query behavior.
Favorite count and updated timestamp
Hide both on mobile:
Desktop behavior remains unchanged.
These are secondary status details and must not force the header to wrap.
Do not delete their runtime updates. They remain available when the viewport widens.
Layout selector
The Dashboard layout switcher is not needed on mobile.
Hide:
Layoutlabel;Full width;Report;2 columns;3 columns.Mobile visual layout
The Dashboard must visually use one normal full-width column at mobile widths, regardless of the saved desktop preference.
This includes overriding Report-mode presentation.
Required behavior:
The exact normal tile minimum height should match the ordinary Dashboard tile rule.
Persisted preference
Do not modify:
Mobile is only a responsive visual override.
When the viewport widens again, the saved desktop layout must return automatically.
Changing orientation must not write new preferences.
Changing layout on desktop must still persist exactly as before.
Filter row
Dashboard filters must render in one non-wrapping horizontal row on mobile.
Current wrapping behavior causes the toolbar to grow vertically as more filters are added.
Use the same interaction pattern as the workbench variable strip:
Requirements:
Filter controls and popovers
All existing filter control kinds must continue to work in the horizontal strip:
Dropdowns and footers already use fixed positioning to escape scrolling containers.
Verify that:
Do not add another mobile-specific filter renderer.
Empty toolbar
When the Dashboard has no filters, hiding the mobile layout selector must not leave an empty sticky toolbar row.
The renderer already knows whether filter controls exist.
Add an explicit class or attribute:
Then:
Equivalent naming is acceptable.
Requirements:
Sticky behavior
Keep the compact mobile header and filter row sticky.
The top area may contain:
It must never contain:
The first Dashboard panel must remain reachable without excessive initial scrolling.
Do not make the complete Dashboard header non-sticky in this issue.
Desktop behavior
At widths above 768 px, preserve the current Dashboard behavior:
This issue is a responsive presentation change only.
Suggested class hooks
Add explicit hooks rather than relying on fragile
:nth-childselectors.Suggested classes:
Existing classes may be reused where already precise:
Example markup changes:
Files
Expected modifications:
Expected tests:
Add or update a focused Dashboard fixture if required.
No schema change.
No saved-query migration.
No new runtime dependency.
Tests
Header at phone width
At a representative viewport such as:
verify:
.dash-headerremains one row;SQL Browsertext is hidden;Very narrow phone
At a width near:
verify:
Layout override
Set each persisted desktop layout:
At mobile width, verify:
Widen the viewport and verify the saved desktop layout returns.
Filter row
With several filter controls:
.var-fieldretains its normal width;Filter variants
Cover:
Verify dropdowns remain visible outside the scroll strip.
No filters
With no Dashboard parameters:
On desktop, the layout selector remains visible.
Refresh state
During Refresh:
Landscape
At a mobile-landscape or narrow-tablet width near the breakpoint:
Accessibility
Verify:
Non-goals
Do not include:
Acceptance criteria
npm testpasses.npm run buildsucceeds.Definition of done
On mobile, the Dashboard uses one compact sticky header row containing only back navigation, the truncated Dashboard title, theme, and Refresh icons.
The layout selector, favorite count, hostname, updated timestamp, and Refresh label are hidden. Dashboard panels use one full-width column without changing the saved desktop preference. Filters occupy one horizontally scrollable row, and Dashboards without filters have no empty toolbar.