Skip to content

Dashboard KPI bands: flat content-sized cards across layout modes #240

Description

@BorisTyshkevich

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:

src/ui/kpi-panel.js

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:

.dash-kpi-stream

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:
Loading…
Loading… 1 row

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:

  1. reserve a new generation for every affected explicit KPI source;
  2. replace each affected source with its loading card immediately;
  3. run requests under the existing concurrency limit;
  4. replace only the winning generation’s source content;
  5. 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:

role="alert"

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

  • Only explicit, favorited Panel-role KPI queries join KPI bands.
  • Auto-detected KPI results remain ordinary Dashboard tiles.
  • Consecutive explicit KPI queries form one shared card stream.
  • A non-KPI Panel breaks the stream.
  • Every KPI band spans all Dashboard columns.
  • Dashboard 2 columns and 3 columns never place explicit KPI panels inside those tile columns.
  • Successful KPI presentation shows no saved-query name.
  • Successful KPI presentation shows no saved-query description.
  • Successful KPI presentation has no gray generic tile shell.
  • Successful KPI presentation has no rows/time/bytes footer.
  • Field labels, descriptions, values, units, deltas, and colors remain visible.
  • KPI cards use controlled unequal content-driven widths.
  • Desktop cards have a 160 px minimum and 320 px maximum.
  • Cards wrap only when the available band width is exhausted.
  • Cards in one wrapped line share a consistent height.
  • Narrow-screen cards become full width.
  • Loading appears as one white KPI-style card per query.
  • Missing-parameter and execution errors appear inside KPI-style cards.
  • State cards identify the saved query.
  • One source failure does not remove sibling cards.
  • Warnings remain visible below the band and identify their query.
  • Request concurrency and stable source order are both preserved.
  • Targeted filter reruns update only affected KPI sources.
  • Layout changes do not execute SQL.
  • Workbench KPI preview remains functionally unchanged.
  • Ordinary Dashboard panels retain their existing tile chrome and layout.
  • Unit and browser tests cover all four Dashboard layout modes.
  • npm test passes.
  • npm run build succeeds.
  • No new runtime dependency is added.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions