Problem
The Dashboard currently renders every favorited query through the same generic tile shell:
saved-query name
saved-query description
panel body
rows · time · bytes
An explicit KPI panel then renders its own KPI-card grid inside that shell.
This produces the wrong composition:
- KPI cards are nested inside a gray Dashboard tile;
- the saved-query name and description repeat information that belongs to the individual KPI cards;
- query statistics consume a footer row that is not useful for KPI presentation;
- the Dashboard
2 columns and 3 columns layouts constrain the complete KPI query to one grid cell;
- the KPI renderer then creates another 2–3-column grid inside that tile;
- several consecutive KPI queries cannot form one continuous KPI strip;
- the current 220–280 px card grid wastes horizontal space for short labels and values.
The Dashboard must treat explicitly configured KPI favorites as a special full-width composition primitive rather than ordinary tiles.
Goal
Render explicitly configured, favorited KPI queries as flat white KPI cards in full-width KPI bands.
A KPI band:
- spans the complete Dashboard content width;
- ignores the selected
Full width, Report, 2 columns, or 3 columns tile layout;
- contains the cards from every consecutive explicit KPI favorite;
- preserves favorite order and result-column order;
- uses one horizontal stream that wraps only when the available width is exhausted;
- has no generic query tile name, description, gray surrounding box, or statistics footer;
- keeps field labels, field descriptions, values, units, deltas, colors, and diagnostics;
- renders loading and errors as compact KPI-style cards;
- renders warnings below the card stream.
Eligibility
A query participates in a Dashboard KPI band only when all of the following are true:
queryFavorite(query) === true
effectiveDashboardRole(query.spec) === "panel"
queryPanel(query)?.cfg?.type === "kpi"
This issue applies only to explicit KPI configuration.
Do not place an auto-detected one-row result into a KPI band.
An unconfigured query that autoPanel() resolves to KPI continues through the ordinary Dashboard tile path and follows the selected Dashboard layout.
Filter and Setup roles never participate in KPI bands.
Grouping
Partition the ordered list of favorited Panel queries into maximal consecutive runs.
Example input:
KPI A
KPI B
Line chart
KPI C
KPI D
Text panel
Table
KPI E
Required layout items:
KPI band: A, B
ordinary tile: Line chart
KPI band: C, D
ordinary tile: Text panel
ordinary tile: Table
KPI band: E
A non-KPI Panel ends the current KPI run.
A Filter or Setup query is already removed by Dashboard role partitioning and does not create a visual separator.
Within one KPI band, card order is:
favorite query order
→ result-column order within each query
Refreshes, filter edits, loading completion order, and request concurrency must never reorder cards.
Dashboard layout interaction
The selected Dashboard layout controls ordinary tiles only.
Every KPI band spans all Dashboard grid columns:
.dash-kpi-band {
grid-column: 1 / -1;
}
This applies in:
Full width
Report
2 columns
3 columns
Changing the Dashboard layout:
- must not re-query KPI data;
- must not split a KPI band into tile columns;
- must not create nested KPI grids;
- may cause the KPI card stream to rewrap because available width changed.
Visual structure
Successful KPI band
┌──────────────┐ ┌───────────────┐ ┌──────────────────┐ ┌─────────────┐
│ SELECT │ │ Errors │ │ Avg duration │ │ Active users│
│ 591 │ │ 0 │ │ 2268.6 ms │ │ 7 │
└──────────────┘ └───────────────┘ └──────────────────┘ └─────────────┘
Warnings, when present
Only the white KPI cards are visible.
Do not render:
- the saved-query name above successful cards;
- the saved-query description above successful cards;
.dash-tile-head;
.dash-tile-desc;
- the gray
.dash-tile shell;
.dash-tile-foot;
- row count;
- elapsed time;
- scanned bytes.
Preserved field presentation
Keep the KPI field-level presentation already owned by panel.fieldConfig:
displayName;
description;
unit;
decimals;
color;
noValue;
- delta label;
- delta unit;
- delta decimals;
- delta semantic color.
The saved-query name and saved-query description are removed only from successful Dashboard KPI presentation.
Workbench KPI preview remains unchanged except for reuse of any extracted card-rendering primitive.
Card width and wrapping
Use controlled unequal widths.
Unequal widths are intentional:
Advantages
- short KPIs remain compact;
- more KPIs fit on one line;
- long values receive the width they need without widening every sibling;
- mixed KPI queries read as one dense stream rather than nested mini-dashboards;
- the layout matches the requested “as minimal as possible” behavior.
Trade-offs
- card right edges are not aligned like equal table columns;
- a wrapped final row may have unused space;
- very different labels can create visible width variation.
These trade-offs are acceptable for KPI cards because each card is an independent fact, not one column in a comparison table.
Keep the result visually controlled through:
- identical padding;
- identical border and radius;
- one consistent gap;
- a shared minimum width;
- a shared maximum width;
- equal height within each wrapped flex line;
- no masonry positioning.
Required layout
.dash-kpi-stream {
display: flex;
flex-wrap: wrap;
align-items: stretch;
gap: 10px;
}
.dash-kpi-stream .kpi-card {
flex: 0 1 auto;
inline-size: fit-content;
min-inline-size: 160px;
max-inline-size: 320px;
}
The card’s intrinsic width is driven by its visible content and padding.
Requirements:
- short label/value cards should normally remain near the minimum;
- longer values, units, delta labels, or field descriptions may widen the card;
- no card may exceed 320 px on desktop;
- labels and descriptions wrap within the maximum;
- numeric values should remain on one line when they fit;
- exceptionally long values may wrap rather than overflow;
- all cards in one flex line stretch to the same height;
- the next card wraps only when it cannot fit in the remaining line width.
Do not compute widths from fixed Dashboard column counts.
Do not use the current:
grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 280px));
for Dashboard KPI bands.
The workbench KPI preview may retain its own grid behavior.
Narrow screens
At the existing narrow/mobile breakpoint, cards become full-row:
@media (max-width: 520px) {
.dash-kpi-stream .kpi-card {
flex-basis: 100%;
min-inline-size: 0;
max-inline-size: none;
inline-size: 100%;
}
}
Stable Dashboard structure
Build KPI bands before query execution from explicit saved configuration.
Do not wait for result classification to decide whether a query belongs to a band.
Suggested layout model:
[
{
kind: "kpi-band",
sources: [queryA, queryB]
},
{
kind: "tile",
source: lineChart
},
{
kind: "kpi-band",
sources: [queryC]
}
]
Each explicit KPI source receives a stable source slot inside its band.
Suggested state:
{
query,
generation,
abortController,
status,
cardsHost,
warnings,
destroy
}
The band owns:
{
el,
stream,
warningHost,
sources
}
A source slot must keep its position while its request changes between:
loading
success
error
unfilled
Do not remove and reappend completed sources based on response order.
Shared KPI card rendering
The current KPI renderer returns one complete .kpi-panel containing its own .kpi-grid.
Dashboard needs the individual card nodes so cards from consecutive KPI queries can share one stream.
Extract a lower-level shared rendering primitive from:
Suggested API:
renderKpiCards(normalized) -> {
cards,
warnings,
errors
}
Where:
cards: HTMLElement[]
warnings: diagnostic[]
errors: diagnostic[]
Requirements:
- value formatting remains shared;
- delta formatting remains shared;
- color application remains shared;
aria-label behavior remains shared;
- field descriptions remain shared;
- no Dashboard-specific query execution logic enters
kpi-panel.js.
Then compose:
Workbench
renderKpiPanel(normalized)
continues to return:
.kpi-panel
.kpi-grid
cards
.kpi-warnings
Dashboard
The KPI band appends each source’s cards directly into the shared:
Dashboard renders warnings separately below the shared stream.
Do not duplicate KPI label/value/delta DOM construction in dashboard.js.
Loading state
Each explicit KPI query starts with one compact KPI-style loading card in its stable source position.
Example:
┌────────────────────────┐
│ Query log health │
│ Loading… │
└────────────────────────┘
Requirements:
- use the saved-query name only to identify the pending source;
- do not show the saved-query description;
- do not show query statistics;
- retain the normal white KPI-card visual vocabulary;
- include the existing spinner;
- streamed progress may update the state text:
When the request completes, replace the single loading card with the result’s zero or more KPI cards without changing source order.
Error and invalid-result states
Errors remain inside the KPI stream as compact white state cards.
Example:
┌────────────────────────────────────┐
│ Query log health │
│ Expected 1 row, got 3 │
└────────────────────────────────────┘
The state card uses:
- saved-query name;
- error message;
- error semantics and accessible alert behavior;
- no saved-query description;
- no statistics footer;
- no generic gray tile.
This includes:
- network errors;
- ClickHouse errors;
- authored FORMAT errors;
- missing or invalid required parameters;
- zero-row/no-data state;
- multiple-row result;
- no eligible KPI fields;
- incompatible explicit KPI result shape.
An error in one KPI source must not remove successful sibling cards from the same band.
Unfilled parameters
An explicit KPI query blocked by missing or invalid parameters renders:
┌────────────────────────────────────┐
│ Query log health │
│ Enter a value for: from │
└────────────────────────────────────┘
It remains in its stable source position.
Warnings
Warnings remain visible below the cards.
Examples:
- unsupported result field skipped;
- stale field metadata target;
- roles re-derived;
- other non-blocking KPI diagnostics.
Render one warning area per KPI band:
SELECT health: Column region has unsupported KPI type String.
Latency KPIs: fieldConfig target old_latency is not present in this result.
Requirements:
- warning order follows source order, then diagnostic order;
- each warning names its saved query because successful cards no longer show query names;
- warnings do not become KPI cards;
- warnings do not use the generic query statistics footer;
- warnings do not block successful sibling cards;
- warning area disappears when empty;
- refresh replaces stale warnings with current warnings.
Errors already represented by state cards must not be duplicated in the warning area.
Ordinary Dashboard tiles
The existing generic Dashboard tile path remains unchanged for:
- charts;
- tables;
- logs;
- text;
- explicit non-KPI panels;
- unconfigured/auto-detected KPI results.
Generic tiles continue to render:
- saved-query name;
- saved-query description;
- panel body;
- statistics footer;
- selected Dashboard layout behavior.
Remove KPI-specific generic-tile styling only where it becomes unused for explicit KPI favorites.
Do not globally remove names, descriptions, shells, or statistics from other panel types.
Execution behavior
KPI band queries keep the existing Dashboard execution semantics:
- one prepared parameter wave;
- existing concurrency ceiling;
- existing request generation guards;
- existing cancellation;
- existing targeted filter reruns;
- existing Refresh behavior;
- existing KPI-owned structured transport;
- exact one-row KPI row limit and diagnostics;
- current value-history recording rules.
This issue changes composition and rendering only.
A KPI band must not serialize its queries merely to preserve visual order. Stable source slots preserve order while requests remain concurrent.
Refresh and filter edits
On Refresh:
- reserve a new generation for every affected explicit KPI source;
- replace each affected source with its loading card immediately;
- run requests under the existing concurrency limit;
- replace only the winning generation’s source content;
- rebuild the band warning area from current source diagnostics.
On a targeted filter edit:
- rerun only affected KPI sources;
- keep unaffected KPI cards visible;
- preserve the band and source order;
- show loading only for affected sources;
- never recreate the complete Dashboard grid.
Accessibility
The shared KPI stream uses:
<div
class="dash-kpi-stream"
role="group"
aria-label="Key performance indicators">
Each successful KPI card retains its existing field-level aria-label.
Loading cards use:
role="status"
aria-live="polite"
Error cards use:
The query name must be included in the accessible name of loading and error cards.
Warnings use a visible status region below the stream.
DOM order must match visual and reading order.
CSS classes
Add:
.dash-kpi-band
.dash-kpi-stream
.dash-kpi-source
.dash-kpi-state-card
.dash-kpi-state-label
.dash-kpi-state-message
.dash-kpi-warnings
.dash-kpi-warning
A source wrapper may use:
.dash-kpi-source {
display: contents;
}
only when its warning/state ownership remains accessible and tested.
Prefer a stable source host whose children participate directly in the band stream without adding a visible surrounding box.
Do not add another nested grid inside .dash-kpi-stream.
Files
Expected modifications:
src/ui/dashboard.js
src/ui/kpi-panel.js
src/styles.css
tests/unit/dashboard.test.js
tests/unit/kpi-panel.test.js
tests/e2e/kpi.html
tests/e2e/dashboard*.spec.js
Modify other focused tests discovered during implementation.
No schema change.
No saved-query migration.
No new runtime dependency.
Implementation order
1. Extract card rendering
- export shared individual KPI-card rendering;
- keep workbench
renderKpiPanel() behavior;
- add unit tests for cards, warnings, and errors.
2. Build Dashboard layout items
- detect explicit favorite KPI queries;
- partition maximal consecutive runs;
- create full-span KPI-band containers;
- keep ordinary query tiles unchanged.
3. Add stable KPI source slots
- loading card;
- success cards;
- unfilled card;
- error card;
- generation and abort lifecycle;
- per-source diagnostics.
4. Flatten successful cards
- append cards from all consecutive sources into one stream;
- preserve query and column order;
- remove generic KPI tile chrome and footer.
5. Add controlled intrinsic sizing
- flex-wrap stream;
- 160 px minimum;
- 320 px maximum;
- equal line height;
- mobile full-width behavior;
- no nested Dashboard KPI grid.
6. Add warning composition
- collect warnings by source;
- render below the band;
- remove stale warnings after rerun.
7. Regression coverage
- all Dashboard layouts;
- mixed KPI/non-KPI sequences;
- loading/error/warning states;
- targeted reruns;
- mobile/narrow rendering;
- workbench KPI preview unchanged.
Tests
Eligibility
- explicit favorite KPI joins a band;
- non-favorite KPI is absent from Dashboard;
- Filter-role query with KPI cfg does not join;
- Setup-role query with KPI cfg does not join;
- auto-detected KPI remains an ordinary tile;
- explicit non-KPI remains an ordinary tile.
Grouping
Verify:
KPI, KPI, chart, KPI, text, KPI, KPI
becomes:
band(2), tile(chart), band(1), tile(text), band(2)
Also cover:
- no KPI;
- one KPI;
- all KPI;
- KPI first;
- KPI last;
- skipped/unconfigured ordinary queries between KPI runs.
Visual chrome
For successful explicit KPI sources:
- no saved-query heading;
- no saved-query description;
- no
.dash-tile wrapper;
- no
.dash-tile-foot;
- no rows/time/bytes text;
- white
.kpi-card nodes remain.
Ordinary panels retain all existing chrome.
Ordering
- query order follows favorites;
- field order follows result columns;
- faster later request cannot move before a slower earlier request;
- targeted rerun cannot reorder the band;
- warning order is deterministic.
Layout modes
In every Dashboard mode:
wide
report
columns-2
columns-3
verify:
- KPI band spans all columns;
- cards share one stream;
- no KPI query is constrained to one tile column;
- changing layout causes no query request;
- ordinary tiles still follow the selected mode.
Width and wrapping
At wide viewport:
- short cards remain close to the minimum width;
- long cards grow without exceeding 320 px;
- cards have unequal widths when content differs;
- cards share one flex line while space remains;
- the next card wraps when space is exhausted;
- cards in one line have equal height;
- no horizontal overflow.
At narrow viewport:
- each card occupies the full available width;
- no card exceeds the viewport;
- labels, descriptions, values, and deltas remain readable.
States
- loading is one white state card per source;
- streamed row progress updates that card;
- missing parameter is an in-stream state card;
- network error is an in-stream alert card;
- zero rows is an in-stream no-data card;
- multiple rows is an in-stream error card;
- no eligible fields is an in-stream error card;
- one source error does not hide sibling source cards;
- successful rerun replaces the state card with KPI cards.
Warnings
- successful cards plus warning;
- several warnings from one source;
- warnings from several sources;
- query name prefixes every warning;
- errors are not duplicated below;
- warning area disappears after a clean rerun;
- warning order remains stable.
Lifecycle
- Refresh shows loading in all KPI source slots;
- targeted filter change reloads only affected KPI sources;
- unaffected cards remain visible;
- stale generation result is discarded;
- abort does not render stale error;
- chart destruction behavior for ordinary tiles is unchanged.
Regression
- workbench KPI preview;
- detached KPI view;
- KPI formatting;
- delta semantics;
- field descriptions;
- explicit KPI error behavior;
- autoPanel;
- Dashboard filters;
- ordinary tile layout;
- text panels;
- Dashboard concurrency;
- build and coverage.
Non-goals
Do not implement:
- automatic conversion of auto-detected KPI tiles into bands;
- persisted KPI layout options;
- user-configurable card width;
- drag-and-drop KPI card ordering;
- manual KPI band breaks;
- masonry layout;
- horizontal scrolling instead of wrapping;
- removal of field-level descriptions;
- removal of generic tile chrome from non-KPI panels;
- changes to KPI result normalization;
- changes to KPI transport;
- schema changes;
- new query execution order.
Acceptance criteria
Definition of done
The Dashboard no longer treats an explicit KPI query as a generic tile containing another card grid.
Explicit favorite KPI queries render as a flat, full-width, ordered stream of compact white KPI cards. Consecutive KPI queries merge visually into one band, cards wrap responsively, loading and errors remain identifiable inside the stream, and warnings remain visible below it.
Problem
The Dashboard currently renders every favorited query through the same generic tile shell:
An explicit KPI panel then renders its own KPI-card grid inside that shell.
This produces the wrong composition:
2 columnsand3 columnslayouts constrain the complete KPI query to one grid cell;The Dashboard must treat explicitly configured KPI favorites as a special full-width composition primitive rather than ordinary tiles.
Goal
Render explicitly configured, favorited KPI queries as flat white KPI cards in full-width KPI bands.
A KPI band:
Full width,Report,2 columns, or3 columnstile layout;Eligibility
A query participates in a Dashboard KPI band only when all of the following are true:
This issue applies only to explicit KPI configuration.
Do not place an auto-detected one-row result into a KPI band.
An unconfigured query that
autoPanel()resolves to KPI continues through the ordinary Dashboard tile path and follows the selected Dashboard layout.Filter and Setup roles never participate in KPI bands.
Grouping
Partition the ordered list of favorited Panel queries into maximal consecutive runs.
Example input:
Required layout items:
A non-KPI Panel ends the current KPI run.
A Filter or Setup query is already removed by Dashboard role partitioning and does not create a visual separator.
Within one KPI band, card order is:
Refreshes, filter edits, loading completion order, and request concurrency must never reorder cards.
Dashboard layout interaction
The selected Dashboard layout controls ordinary tiles only.
Every KPI band spans all Dashboard grid columns:
This applies in:
Changing the Dashboard layout:
Visual structure
Successful KPI band
Only the white KPI cards are visible.
Do not render:
.dash-tile-head;.dash-tile-desc;.dash-tileshell;.dash-tile-foot;Preserved field presentation
Keep the KPI field-level presentation already owned by
panel.fieldConfig:displayName;description;unit;decimals;color;noValue;The saved-query name and saved-query description are removed only from successful Dashboard KPI presentation.
Workbench KPI preview remains unchanged except for reuse of any extracted card-rendering primitive.
Card width and wrapping
Use controlled unequal widths.
Unequal widths are intentional:
Advantages
Trade-offs
These trade-offs are acceptable for KPI cards because each card is an independent fact, not one column in a comparison table.
Keep the result visually controlled through:
Required layout
The card’s intrinsic width is driven by its visible content and padding.
Requirements:
Do not compute widths from fixed Dashboard column counts.
Do not use the current:
for Dashboard KPI bands.
The workbench KPI preview may retain its own grid behavior.
Narrow screens
At the existing narrow/mobile breakpoint, cards become full-row:
Stable Dashboard structure
Build KPI bands before query execution from explicit saved configuration.
Do not wait for result classification to decide whether a query belongs to a band.
Suggested layout model:
Each explicit KPI source receives a stable source slot inside its band.
Suggested state:
The band owns:
A source slot must keep its position while its request changes between:
Do not remove and reappend completed sources based on response order.
Shared KPI card rendering
The current KPI renderer returns one complete
.kpi-panelcontaining its own.kpi-grid.Dashboard needs the individual card nodes so cards from consecutive KPI queries can share one stream.
Extract a lower-level shared rendering primitive from:
Suggested API:
Where:
Requirements:
aria-labelbehavior remains shared;kpi-panel.js.Then compose:
Workbench
continues to return:
Dashboard
The KPI band appends each source’s
cardsdirectly into the shared:Dashboard renders warnings separately below the shared stream.
Do not duplicate KPI label/value/delta DOM construction in
dashboard.js.Loading state
Each explicit KPI query starts with one compact KPI-style loading card in its stable source position.
Example:
Requirements:
When the request completes, replace the single loading card with the result’s zero or more KPI cards without changing source order.
Error and invalid-result states
Errors remain inside the KPI stream as compact white state cards.
Example:
The state card uses:
This includes:
An error in one KPI source must not remove successful sibling cards from the same band.
Unfilled parameters
An explicit KPI query blocked by missing or invalid parameters renders:
It remains in its stable source position.
Warnings
Warnings remain visible below the cards.
Examples:
Render one warning area per KPI band:
Requirements:
Errors already represented by state cards must not be duplicated in the warning area.
Ordinary Dashboard tiles
The existing generic Dashboard tile path remains unchanged for:
Generic tiles continue to render:
Remove KPI-specific generic-tile styling only where it becomes unused for explicit KPI favorites.
Do not globally remove names, descriptions, shells, or statistics from other panel types.
Execution behavior
KPI band queries keep the existing Dashboard execution semantics:
This issue changes composition and rendering only.
A KPI band must not serialize its queries merely to preserve visual order. Stable source slots preserve order while requests remain concurrent.
Refresh and filter edits
On Refresh:
On a targeted filter edit:
Accessibility
The shared KPI stream uses:
Each successful KPI card retains its existing field-level
aria-label.Loading cards use:
Error cards use:
The query name must be included in the accessible name of loading and error cards.
Warnings use a visible status region below the stream.
DOM order must match visual and reading order.
CSS classes
Add:
A source wrapper may use:
only when its warning/state ownership remains accessible and tested.
Prefer a stable source host whose children participate directly in the band stream without adding a visible surrounding box.
Do not add another nested grid inside
.dash-kpi-stream.Files
Expected modifications:
Modify other focused tests discovered during implementation.
No schema change.
No saved-query migration.
No new runtime dependency.
Implementation order
1. Extract card rendering
renderKpiPanel()behavior;2. Build Dashboard layout items
3. Add stable KPI source slots
4. Flatten successful cards
5. Add controlled intrinsic sizing
6. Add warning composition
7. Regression coverage
Tests
Eligibility
Grouping
Verify:
becomes:
Also cover:
Visual chrome
For successful explicit KPI sources:
.dash-tilewrapper;.dash-tile-foot;.kpi-cardnodes remain.Ordinary panels retain all existing chrome.
Ordering
Layout modes
In every Dashboard mode:
verify:
Width and wrapping
At wide viewport:
At narrow viewport:
States
Warnings
Lifecycle
Regression
Non-goals
Do not implement:
Acceptance criteria
2 columnsand3 columnsnever place explicit KPI panels inside those tile columns.npm testpasses.npm run buildsucceeds.Definition of done
The Dashboard no longer treats an explicit KPI query as a generic tile containing another card grid.
Explicit favorite KPI queries render as a flat, full-width, ordered stream of compact white KPI cards. Consecutive KPI queries merge visually into one band, cards wrap responsively, loading and errors remain identifiable inside the stream, and warnings remain visible below it.