Skip to content

Commit 2c8e0fe

Browse files
committed
docs(webapp): tighten metrics layout docs and queue release notes
1 parent cec5905 commit 2c8e0fe

3 files changed

Lines changed: 25 additions & 105 deletions

File tree

.server-changes/queue-override-reject-above-limit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: breaking
44
---
55

6-
Setting a queue's concurrency limit higher than the environment limit is now rejected with a clear error instead of being silently reduced to the maximum. This applies to the queue concurrency override API and the dashboard. Existing overrides are unaffected — the change only affects new attempts that exceed the environment limit.
6+
Setting a queue's concurrency limit above the environment limit is now rejected with a clear error instead of being silently capped. Existing overrides are unaffected.

.server-changes/queue-pages-live-and-clarity.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: improvement
44
---
55

6-
The queue pages now keep their live numbers fresh automatically (updates pause while the tab is in the background), and the charts are easier to read: clearer axis labels, hover explanations for the headline stats, and the busiest concurrency key is named right on the chart.
6+
Queue pages now refresh their live numbers automatically, and the charts are clearer: better axis labels, hover explanations for the headline stats, and the busiest concurrency key named on the chart.

apps/webapp/app/components/layout/MetricsLayout.tsx

Lines changed: 23 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,33 @@
11
/**
22
* MetricsLayout — a compound layout for metric / dashboard pages.
33
*
4-
* PHILOSOPHY — slots own their chrome. This family exists so that non-designers (and AI agents)
5-
* can compose a beautiful, consistent metrics page purely by picking slots and setting closed,
6-
* semantic props. There is deliberately NO freeform `className` on any slot: a page cannot restyle
7-
* the spacing, borders or gutters of the layout, because that is exactly how metric pages drifted
8-
* apart before. The slots bake ONE visual rhythm (a 12px grid gap, a 12px page gutter, a doubled
9-
* separation before the content, a pinned 40px filter bar). If you find yourself wanting a class on
10-
* a slot, you probably need a new semantic variant instead — talk to design and add a closed prop
11-
* (like `kind` or `inset`) rather than reopening `className`.
4+
* Slots bake all the chrome; there is no `className` on any slot, so pages can't drift apart on
5+
* spacing. Need a variant? Add a closed prop (`kind`, `inset`), don't reopen `className`.
126
*
13-
* Every metrics page reads top-to-bottom as the same slots:
7+
* Slots, top to bottom:
8+
* - `Filters` — pinned 40px bar under the NavBar. Left/right clusters are child divs.
9+
* - `Grid` — tiles; columns derived from tile count unless `columns` is set. `kind="charts"`
10+
* bakes the fixed chart-row height.
11+
* - `Content` — table / tabs below the tiles. Full-bleed by default; `inset` for a padded column.
1412
*
15-
* 1. `MetricsLayout.Filters` — the pinned bar under the NavBar (search, TimeFilter, pagination…).
16-
* It is hoisted out of the scroll container so it stays put while the page scrolls. Baked
17-
* chrome: a 40px bar with a bottom border. Pages express left/right clusters as child divs
18-
* (e.g. left = search + TimeFilter, right = pagination); `justify-between` spreads them.
19-
* 2. `MetricsLayout.Grid` — one or more grids of tiles (stat BigNumbers, chart cards). The grid
20-
* adapts its column count to the number of tiles unless you pass an explicit `columns` spec,
21-
* and bakes the page gutter + grid gap. `kind="charts"` bakes the fixed chart-row height.
22-
* 3. `MetricsLayout.Content` — the tabs / table / list below the tiles. Full-bleed by default
23-
* (a list table spans edge to edge with its own top border); pass `inset` for a padded column.
24-
* Content always bakes a doubled separation (24px) above it, so the blocks read as a distinct
25-
* band from the content below.
13+
* Optional:
14+
* - `Sidebar` — a persistent right-hand panel; fixed `width` or `resizable`. Present ⇒ Root
15+
* switches to `[main | sidebar]`; absent ⇒ single column.
16+
* - `scroll` on Root — `"page"` (default): the whole page scrolls as one. `"regions"`: Root owns
17+
* no scroll, the page composes its own scrolling areas.
2618
*
27-
* Two optional structural capabilities extend the basic top-to-bottom column:
19+
* Purely presentational. Lives inside a `PageContainer` after the `NavBar`.
2820
*
29-
* - `MetricsLayout.Sidebar` — a persistent side panel rendered to the RIGHT of the main column
30-
* (main content left, sidebar right, full height). Drop a single `<MetricsLayout.Sidebar>`
31-
* anywhere among Root's children and Root switches to a `[main | sidebar]` horizontal layout;
32-
* omit it and nothing changes. The sidebar is fixed-width by default (`width`), or set
33-
* `resizable` to make the split draggable via the shared Resizable primitives.
34-
*
35-
* - `scroll` on Root — chooses who owns the vertical scroll:
36-
* - `"page"` (default): Root owns a single `overflow-y-auto` and lays the slots out as a
37-
* `flex` column with the standard vertical rhythm; the WHOLE page (filters aside) scrolls
38-
* as one. This is what every current metrics page wants.
39-
* - `"regions"`: Root does NOT create a scroll container and does NOT impose the column
40-
* rhythm — it only bounds the height as a bare `flex` column. The page composes its own
41-
* independently-scrolling areas inside the slots (e.g. a fixed toolbar over a scrolling
42-
* table, or a vertical resizable split). Use this when a single page-level scroll would be
43-
* wrong.
44-
*
45-
* The family is purely presentational — slots, grids, a sidebar and scroll ownership only, no data
46-
* logic. It is meant to live inside a `PageContainer` right after the `NavBar`.
47-
*
48-
* @example List page (full-bleed table, count-derived grids)
49-
* ```tsx
50-
* <PageContainer>
51-
* <NavBar>…</NavBar>
52-
* <MetricsLayout.Root>
53-
* <MetricsLayout.Filters>
54-
* <div className="flex items-center gap-2">…search + TimeFilter…</div>
55-
* <PaginationControls … />
56-
* </MetricsLayout.Filters>
57-
* <MetricsLayout.Grid>…4 stat tiles…</MetricsLayout.Grid>
58-
* <ChartSyncProvider>
59-
* <MetricsLayout.Grid kind="charts">…4 chart tiles…</MetricsLayout.Grid>
60-
* </ChartSyncProvider>
61-
* <MetricsLayout.Content>…full-width table…</MetricsLayout.Content>
62-
* </MetricsLayout.Root>
63-
* </PageContainer>
64-
* ```
65-
*
66-
* @example Detail page (a single padded column)
21+
* @example
6722
* ```tsx
6823
* <MetricsLayout.Root>
6924
* <MetricsLayout.Filters>
7025
* <div className="flex items-center gap-2">…search + TimeFilter…</div>
26+
* <PaginationControls … />
7127
* </MetricsLayout.Filters>
72-
* <MetricsLayout.Grid>…3 stat tiles…</MetricsLayout.Grid>
73-
* <MetricsLayout.Content inset>
74-
* <TabContainer>…</TabContainer>
75-
* …active view (charts / keys)…
76-
* </MetricsLayout.Content>
77-
* </MetricsLayout.Root>
78-
* ```
79-
*
80-
* @example Page with a persistent config sidebar (fixed-width)
81-
* ```tsx
82-
* <MetricsLayout.Root>
83-
* <MetricsLayout.Filters>…</MetricsLayout.Filters>
84-
* <MetricsLayout.Content>…</MetricsLayout.Content>
85-
* <MetricsLayout.Sidebar width="380px">…config panel…</MetricsLayout.Sidebar>
86-
* </MetricsLayout.Root>
87-
* ```
88-
*
89-
* @example Resizable sidebar + independently-scrolling regions
90-
* ```tsx
91-
* // In the loader, hydrate the persisted split from a cookie:
92-
* const sidebarSnapshot = await getResizableSnapshot(request, "my-page-sidebar");
93-
*
94-
* <MetricsLayout.Root scroll="regions">
95-
* <div className="flex h-10 shrink-0 items-center …">…toolbar…</div>
96-
* <div className="min-h-0 flex-1 overflow-y-auto">…scrolling body…</div>
97-
* <MetricsLayout.Sidebar
98-
* resizable
99-
* autosaveId="my-page-sidebar"
100-
* snapshot={sidebarSnapshot}
101-
* min="280px"
102-
* defaultSize="380px"
103-
* max="500px"
104-
* >
105-
* …config panel…
106-
* </MetricsLayout.Sidebar>
28+
* <MetricsLayout.Grid>…stat tiles…</MetricsLayout.Grid>
29+
* <MetricsLayout.Grid kind="charts">…chart tiles…</MetricsLayout.Grid>
30+
* <MetricsLayout.Content>…table…</MetricsLayout.Content>
10731
* </MetricsLayout.Root>
10832
* ```
10933
*/
@@ -204,10 +128,8 @@ type MetricsLayoutSidebarProps = {
204128
*/
205129
width?: string;
206130
/**
207-
* When true, the `[main | sidebar]` split becomes draggable using the shared Resizable
208-
* primitives. Persist the split by passing a stable `autosaveId` (the primitive writes the
209-
* split to a cookie of that name) together with a `snapshot` read back from that cookie in the
210-
* loader via `getResizableSnapshot(request, autosaveId)`.
131+
* Makes the split draggable. To persist it, pass an `autosaveId` (written to a cookie) plus the
132+
* `snapshot` read back in the loader via `getResizableSnapshot(request, autosaveId)`.
211133
*/
212134
resizable?: boolean;
213135
/** Resizable only: min width of the sidebar panel. Defaults to `"280px"`. */
@@ -242,11 +164,9 @@ function isFiltersElement(child: ReactNode): child is ReactElement {
242164
return isValidElement(child) && child.type === MetricsLayoutFilters;
243165
}
244166

245-
// The main (left) column. The Filters slot is hoisted OUT of the scroll container so it stays
246-
// pinned while the tiles/content scroll underneath (the dashboards' `[auto | 1fr]` pattern —
247-
// sticky without z-index/backdrop bookkeeping). In `"page"` mode the rest scrolls as one and gets
248-
// the baked column rhythm (vertical gap + top/bottom padding); in `"regions"` mode the page
249-
// composes its own scrolling areas, so the container stays bare.
167+
// The main (left) column. Filters is hoisted out of the scroll container so it stays pinned while
168+
// the rest scrolls. `"page"` scrolls as one with the baked column rhythm; `"regions"` stays bare so
169+
// the page owns its own scrolling.
250170
function MetricsLayoutMain({ children, scroll }: { children: ReactNode; scroll: MetricsScroll }) {
251171
const arr = Children.toArray(children);
252172
const filters = arr.find(isFiltersElement);

0 commit comments

Comments
 (0)