Edit the whole dashboard, and let the grid follow its canvas

The editor only ever arranged the first section, so the demo's Home
dashboard lost 16 of its 25 widgets the moment it was edited. A page's
sections are now read as one arrangement — each pushed below the one
before it — and written back as one, which is the shape a dashboard was
already heading for: one dashboard, one canvas, and the panel rail for
the several-dashboards story. The page tabs that story made dead are
gone; PageDef/SectionDef stay in the schema and a page the editor does
not show round-trips untouched.

The row height derives from the canvas as the column width already did,
so the same arrangement is the same picture on a 7" panel as on a 4K
one. The uPlot rules move beside UplotChart, where a chart on Health is
styled without a dashboard having been visited first, and the bar's
readout travels on one property instead of jumping sides at 30%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tULRZJUkZsw7rMJ3h4xvu
This commit is contained in:
2026-08-22 12:22:37 +02:00
co-authored by Claude Opus 5
parent 48955a6787
commit eb2e6bf6d2
6 changed files with 216 additions and 162 deletions
@@ -30,7 +30,9 @@
.widget-grid {
display: grid;
gap: 0.75rem;
grid-auto-rows: 5rem;
/* The row height follows the canvas, handed in beside the column count;
5rem is what a 1920x1080 panel at 12 columns derives. */
grid-auto-rows: var(--row-height, 5rem);
grid-template-columns: repeat(var(--widget-cols), minmax(0, 1fr));
/* Stacked, this is a flex item in an `auto` track, and its automatic minimum
size is the widest thing any widget holds — one long message name would
@@ -69,7 +71,7 @@
.widget-grid.widget-stacked .widget-cell {
grid-column: auto;
grid-row: auto;
height: calc(var(--h) * 5rem + (var(--h) - 1) * 0.75rem);
height: calc(var(--h) * var(--row-height, 5rem) + (var(--h) - 1) * 0.75rem);
}
/*
@@ -122,48 +124,6 @@
}
}
/*
* uPlot, routed through the tokens. Its own legend is the hover readout as
* well — the value each line carried at the cursor — so it is styled as chart
* furniture rather than replaced.
*/
.u-legend {
font-size: 0.75rem;
color: var(--muted-foreground);
margin-top: 0.25rem;
}
/* Series labels default to the message name, which has no spaces to break at
— and a table cannot lay out below its min-content width. */
.u-legend th {
overflow-wrap: anywhere;
}
.u-legend .u-marker {
width: 0.5rem;
height: 0.5rem;
border-width: 2px;
border-radius: 9999px;
}
.u-legend .u-value {
font-variant-numeric: tabular-nums;
color: var(--foreground);
}
.u-legend .u-series.u-off {
opacity: 0.4;
}
.u-cursor-x,
.u-cursor-y {
border-color: color-mix(in srgb, var(--muted-foreground) 55%, transparent);
}
.u-select {
background: color-mix(in srgb, var(--primary) 12%, transparent);
}
/* react-grid-layout ships a red placeholder and a black handle glyph. */
.react-grid-item.react-grid-placeholder {
background: var(--primary);